Intelligent Model Merging for Diffusion Models
When you have multiple fine-tuned variants of a base model (e.g., Chroma v48 with variants for detail, speed, realism, etc.), you want to combine the best qualities of each into a single model. Naive linear merging causes destructive interference — unique features cancel each other out.
OrthoMerge Studio solves this by:
- Analyzing how each model differs from the base (and from each other)
- Finding the optimal delta-base for each model (not always the global base!)
- Detecting redundancy (models that are too similar to both include)
- Generating merge scripts that use orthogonal projection to preserve unique features
- Letting you override everything through a visual mixer
Linear Merge (naive):
result = 0.5 × model_A + 0.5 × model_B
→ Shared features reinforced, unique features diluted by 50%
OrthoMerge (orthogonal projection):
delta_A = model_A - base
delta_B = model_B - base
delta_B_ortho = delta_B - projection(delta_B onto delta_A)
result = base + delta_A + delta_B_ortho
→ Both unique contributions fully preserved
The key insight: by projecting each delta onto the orthogonal complement of the others, no information is lost. Each model's unique contribution survives the merge intact.
Instead of always computing deltas against the global base, the system tests every possible parent for each model:
Traditional: delta = ModelA - Base (magnitude: 1200)
Optimized: delta = ModelA - ModelC (magnitude: 340)
→ 72% more focused, less noise
This works because some fine-tunes share a common ancestor. Computing the delta against a closer relative produces a cleaner, more focused signal.
The optimizer uses greedy initialization followed by iterative local search, with cycle detection to prevent circular dependencies (A←B←A).
All pairwise dot products between all models (including Base) are computed in a single streaming pass through the weights:
dot(δ_A←B, δ_C←D) = dot(A,C) - dot(A,D) - dot(B,C) + dot(B,D)
Any delta-base combination can be evaluated without reloading model files. For 6 models this gives complete information about all 7⁶ = 117,649 possible delta-base assignments from a single analysis pass.
Every parameter is pre-filled from the analysis but fully editable:
| ✓ | Model | Delta From | Alpha | Repeat | Info |
|---|---|---|---|---|---|
| ☑ | detail-calibrated | base | 1.0 | 2× | High orthogonality → 2× recommended |
| ☑ | Chroma1-HD | chromav47 | 1.0 | 1× | Via chromav47: 45% more focused |
| ☑ | flash-heun | base | 0.8 | 1× | Standard delta (mag=890) |
| ☐ | RL47 | base | 0.0 | 1× | Redundant to Chroma1-HD (cos=0.84) |
What you can do:
- ➕ Add rows and ➖ Remove rows with a single click
- Change delta-base per model via dropdown (any model or base)
- Adjust alpha per delta (0.0–2.0)
- Set repeat count (1–3× for amplification)
- Re-enable excluded models by checking the enable box
- Override any automatic decision — the analysis is a suggestion, not a mandate
Reduced from 6 matrices to 3 essential metrics:
| Metric | What It Tells You | Why It Matters |
|---|---|---|
| Cosine Similarity | Direction similarity of deltas | High → redundant, low → complementary |
| New Information % | Perpendicular component | Higher → more unique contribution |
| Conflict Ratio | Weights where deltas disagree | High → destructive interference risk |
Removed in v5 (compared to v4): Spearman Rank (redundant to Cosine), Block Coherence (too abstract), Sparsity Score (questionable assumptions).
Preview any model (including your freshly merged result) directly through the ComfyUI API — no need to switch applications. The preview tab builds and submits ComfyUI workflows programmatically.
| Requirement | Version | Purpose |
|---|---|---|
| Python | 3.10+ | Runtime |
| PyTorch | 2.0+ | Tensor operations |
| safetensors | latest | Model file I/O |
| Gradio | 4.0+ | Web UI |
| sd-mecha | latest | Merge execution |
| OrthoMerge | latest | Orthogonal merge algorithm |
| ComfyUI | latest | Preview generation (optional) |
# Clone the repository
git clone https://github.com/YOUR_USERNAME/orthomerge-studio.git
cd orthomerge-studio
# Install Python dependencies
pip install gradio safetensors torch
# Install sd-mecha (required for merge execution)
pip install sd-mecha
# Copy OrthoMerge module to your working directory
# (or ensure it's importable from your Python path)
cp /path/to/OrthoMerge.py .python -c "
import gradio, safetensors, torch, sd_mecha
from OrthoMerge import orthomergev2
print('All dependencies OK')
"orthomerge-studio/
├── ortho_studio_v5.py # GUI (Gradio web interface)
├── analyze_deltas_v5.py # Backend (analysis + optimization engine)
├── OrthoMerge.py # OrthoMerge algorithm (external dependency)
├── README.md # This file
├── LICENSE # MIT License
└── examples/
└── chroma_merge.json # Example mixer configuration
python ortho_studio_v5.pyOpens automatically at http://localhost:7860.
For detailed logging:
python ortho_studio_v5.py --verbose| Step | Action | Where |
|---|---|---|
| 1 | Select architecture (Chroma, Flux, Z-Image, Custom) | Top bar |
| 2 | Choose base model (e.g., chromav48.safetensors) |
Top bar |
| 3 | Select models to analyze (or leave empty for all) | Top bar |
| 4 | Click 🔬 Analyse + Optimierung starten | Tab 1: Analyse |
| 5 | Review the Mixer — adjust delta-bases, alphas, repeats | Tab 2: Mixer |
| 6 | Click 📝 Script generieren | Tab 2: Mixer |
| 7 | Click 🚀 Merge to execute | Tab 3: Merge |
| 8 | Generate test images via ComfyUI | Tab 4: Preview |
# Analyze all models in a directory
python analyze_deltas_v5.py /path/to/base.safetensors /path/to/models/ \
--arch chroma-flow --fix
# Analyze specific models
python analyze_deltas_v5.py base.safetensors modelA.safetensors modelB.safetensors \
--arch chroma-flow --rank-samples 10CLI Options:
| Flag | Description | Default |
|---|---|---|
--arch ARCH |
sd-mecha architecture config string | (required) |
--fix |
Auto-fix incompatible models | off |
--rank-samples N |
Number of keys for SVD analysis | 5 |
Detects and optionally repairs common compatibility issues:
| Issue | Detection | Auto-Fix |
|---|---|---|
| CLIP/text encoder keys in model | Key prefix matching | Strip non-diffusion keys |
| VAE keys in model | Key prefix matching | Strip VAE keys |
model.diffusion_model. prefix |
Prefix check | Remove prefix |
| FP8 quantized weights | Dtype inspection | Convert to BF16 |
.norm.weight vs .norm.scale |
Key name mismatch | Remap keys |
| Less than 50% key overlap | Set intersection | Attempt remap, skip if failed |
Streams through all model weights once, loading only one key at a time.
Computed per key:
- Dot products between all pairs (Base + all models)
- Delta conflict counts (weights where model deltas point in opposite directions)
- Per-block norms (energy distribution across transformer blocks)
- Key magnitudes (for redundancy analysis)
- Outlier tracking (maximum absolute delta values)
Memory efficiency: For N models with K keys, memory usage is O(N) per key, not O(N × K). Total time complexity is O(K × N²).
For the largest tensors (configurable, default top 5 by parameter count), performs Singular Value Decomposition on each delta.
Effective Rank:
For delta matrix D, compute singular values σ₁ ≥ σ₂ ≥ ... ≥ σₙ
Find smallest r such that Σᵢ₌₁ʳ σᵢ² ≥ 0.9 × Σᵢ₌₁ⁿ σᵢ²
Effective rank = r / n
| Effective Rank | Interpretation | Rating |
|---|---|---|
| < 0.3 | Focused, targeted fine-tune | ★★★ |
| 0.3 – 0.6 | Moderate changes | ★★ |
| > 0.6 | Diffuse, noisy changes | ★ |
Signal-to-Noise Ratio (SNR):
Signal = energy in top 10% of singular values
Noise = energy in remaining 90%
SNR = Signal / Noise
High SNR indicates a clean training signal. Low SNR suggests noisy or overfitted weights.
Three key matrices computed:
- Cosine Similarity — direction overlap between deltas
- New Information % — perpendicular (unique) component:
1 - cos² - Conflict Ratio — fraction of weights where deltas disagree
Exhaustive optimization algorithm:
1. GREEDY START
For each model Mᵢ:
Test all parents P ∈ {Base, M₁, ..., Mₙ} \ {Mᵢ}
Pick P that minimizes ‖Mᵢ - P‖
2. LOCAL SEARCH (up to 50 iterations)
For each model Mᵢ:
For each alternative parent P:
If swapping improves total orthogonality score → accept swap
3. CYCLE ELIMINATION
If A←B←A detected:
Break cycle by resetting the weaker link to Base
4. REDUNDANCY DETECTION
If cos(δᵢ, δⱼ) > 0.8 after optimization:
Disable the lower-quality model
5. REPEAT RECOMMENDATION
If avg new_info(δᵢ, δⱼ) > 0.85 for all j ≠ i:
Recommend 2× repeat for model i
The core insight: if we store dot(Xᵢ, Xⱼ) for all model pairs, we can compute the dot product of any delta pair without touching the model files:
Given: dot(X, Y) stored for all X, Y ∈ {Base, M₁, ..., Mₙ}
‖X‖² = dot(X, X) also stored
Then: dot(A-B, C-D) = dot(A,C) - dot(A,D) - dot(B,C) + dot(B,D)
‖A-B‖ = sqrt(‖A‖² + ‖B‖² - 2·dot(A,B))
cos(A-B, C-D) = dot(A-B, C-D) / (‖A-B‖ · ‖C-D‖)
This allows evaluating all (N+1)ᴺ possible delta-base assignments from a single O(K × N²) streaming pass.
For a set of delta assignments {δᵢ = Mᵢ - Pᵢ}:
Total Orthogonality = mean over all pairs (i,j): 1 - cos²(δᵢ, δⱼ)
Range: 0 (all parallel) to 1 (all perpendicular)
Higher = deltas are more independent = less information loss during merge
Quality = 0.40 × Focus
+ 0.40 × SNR_normalized
+ Survival_bonus
- Outlier_penalty
where:
Focus = 1 - effective_rank (range 0–1, higher = more focused)
SNR_normalized = min(1, log₁₀(SNR) / 2) (SNR of 100 → score 1.0)
Survival_bonus = min(0.2, max(0, (survival% - 30) / 200))
Outlier_penalty = min(0.15, max(0, (max_outlier - 5) / 50))
Quality Grades:
| Grade | Score Range | Interpretation |
|---|---|---|
| A+ | > 0.80 | Excellent — focused, clean, high signal |
| A | 0.65 – 0.80 | Very good — reliable merge candidate |
| B | 0.50 – 0.65 | Good — usable with some noise |
| C | 0.35 – 0.50 | Fair — consider with caution |
| D | < 0.35 | Poor — likely noisy or overfitted |
Estimates how much of each delta survives in an averaged merge:
For unit vectors uᵢ = δᵢ / ‖δᵢ‖:
avg_direction = Σ uᵢ
survival(i) = dot(uᵢ, avg_direction / ‖avg_direction‖) × 100%
Models aligned with the average direction survive well. Orthogonal models lose signal in naive averaging — which is exactly why OrthoMerge exists.
| Parameter | UI Element | Range | Description |
|---|---|---|---|
| Enabled | Checkbox | on / off | Include this delta in the merge |
| Model | Dropdown | all models | The fine-tuned model to use |
| Delta From | Dropdown | base + all models |
Parent for delta computation |
| Alpha (α) | Slider | 0.0 – 2.0 | Scaling factor for this delta. 1.0 = full strength |
| Repeat (×) | Slider | 1 – 3 | How many times to include this delta (amplification) |
| Info | Text (read-only) | — | Explanation of the automatic suggestion |
| Parameter | Options | Default | Description |
|---|---|---|---|
| theta_agg | mean, median, sum |
mean |
Aggregation method for projection angles |
| conflict_aware | true, false |
false |
Detect and handle weight conflicts during merge |
| direction_weight | theta, magnitude, equal |
theta |
How to weight delta directions in orthogonal projection |
| Texture Boost Model | any model or (none) |
(none) |
Post-merge add_difference source for texture preservation |
| Texture Boost Alpha | 0.0 – 0.5 | 0.0 | Strength of the texture boost |
After analysis, the mixer is automatically populated based on the optimization results:
| Condition | Action | Reasoning |
|---|---|---|
| Model is closer to another model than to base | Set Delta From to that model | Produces a more focused, cleaner delta |
| New info > 85% vs. all others | Set Repeat = 2 | Highly orthogonal — amplify its unique contribution |
| Cosine > 0.8 to a higher-quality model | Set Enabled = false, α = 0.0 | Redundant — would dilute without adding value |
| Null delta (magnitude ≈ 0) | Set Enabled = false | No meaningful changes to merge |
| All other cases | Enabled = true, α = 1.0, × 1 | Conservative defaults |
Re-enable an excluded model:
The analyzer excluded RL47 as redundant to flash-heun. But you know RL47 has specific lighting improvements you want.
→ Check the enable box, set alpha to 0.5 for a lighter contribution.
Change delta-base:
The analyzer suggests Chroma1-HD ← chromav47. But you know Chroma1-HD was actually fine-tuned from chromav46.
→ Change the "Delta From" dropdown to chromav46 (if available in the model list).
Amplify a specific model:
You want detail-calibrated to have extra influence because photorealistic skin detail is your priority.
→ Set repeat to 3× or increase alpha to 1.5.
Add a model not in the original selection:
You realize you forgot to include a model in the initial analysis. → Click ➕ to add a row, select the model from the dropdown, choose its delta-base manually.
| Architecture | Config String | Default Model Directory |
|---|---|---|
| Flux.2 Klein | flux2-klein |
~/ComfyUI/models/diffusion_models/Flux/ |
| Chroma | chroma-flow |
~/ComfyUI/models/diffusion_models/Chroma/ |
| Z-Image | zimage-base |
~/ComfyUI/models/diffusion_models/Z-Image/ |
| Custom | (user-defined) | (user-defined) |
To add a new architecture, edit the DEFAULT_DIRS and ARCH_CONFIGS dictionaries in ortho_studio_v5.py, or simply select "Custom" in the UI and enter the path and config string manually.
The generated Python scripts follow this pattern:
import sd_mecha, torch
from OrthoMerge import orthomergev2
from safetensors.torch import load_file, save_file
# === Configuration ===
MODEL_DIR = "/path/to/models"
ARCH = "chroma-flow"
BASE_FILE = "chromav48.safetensors"
OUTPUT_NAME = "OrthoMerge_result.safetensors"
# === Load base and models ===
base = sd_mecha.model(f"{MODEL_DIR}/{BASE_FILE}", ARCH)
model_a = sd_mecha.model(f"{MODEL_DIR}/modelA.safetensors", ARCH)
model_b = sd_mecha.model(f"{MODEL_DIR}/modelB.safetensors", ARCH)
parent_c = sd_mecha.model(f"{MODEL_DIR}/chromav47.safetensors", ARCH)
# === Compute deltas (with optimized delta-bases) ===
subtract = sd_mecha.subtract
delta_a = subtract(model_a, base) # Standard: vs base
delta_b = subtract(model_b, parent_c) # Lineage-optimized: vs v47
# === OrthoMerge ===
merged = orthomergev2(base, delta_a, delta_a, delta_b, # delta_a repeated 2×
alpha=1.0, theta_agg="mean", conflict_aware=False,
direction_weight="theta")
# === Optional: Texture boost ===
tex_delta = subtract(texture_model, base)
new = sd_mecha.add_difference(merged, tex_delta, alpha=0.2)
# === Execute merge ===
sd_mecha.merge(new, output=f"{MODEL_DIR}/{OUTPUT_NAME}",
merge_device="cpu", merge_dtype=torch.float32,
output_dtype=torch.bfloat16, threads=1)
# === Post-merge fix ===
print("Post-Merge: Checking missing keys + NaN...")
base_sd = load_file(f"{MODEL_DIR}/{BASE_FILE}")
merged_sd = load_file(f"{MODEL_DIR}/{OUTPUT_NAME}")
# Restore any keys missing from the merge
added = 0
for k, v in base_sd.items():
if k not in merged_sd:
merged_sd[k] = v
added += 1
# Repair any NaN or Inf values
bad_keys = []
for k, v in merged_sd.items():
if not torch.isfinite(v.float()).all():
bad_keys.append(k)
merged_sd[k] = base_sd.get(k, torch.zeros_like(v))
if added:
print(f" {added} missing keys restored")
if bad_keys:
print(f" {len(bad_keys)} NaN/Inf keys repaired")
save_file(merged_sd, f"{MODEL_DIR}/{OUTPUT_NAME}")
print(f"Done: {OUTPUT_NAME} ({len(merged_sd)} keys)")| Variable | Default | Description |
|---|---|---|
COMFYUI_DIR |
~/ComfyUI |
Path to ComfyUI installation |
COMFYUI_API |
http://127.0.0.1:8188 |
ComfyUI API endpoint |
# Example: custom ComfyUI location
export COMFYUI_DIR="/opt/ComfyUI"
export COMFYUI_API="http://192.168.1.100:8188"python ortho_studio_v5.py [OPTIONS]
Options:
--verbose, -v Enable detailed console loggingThe GUI launches on 0.0.0.0:7860 by default (accessible from other machines on your network).
python analyze_deltas_v5.py BASE MODELS [OPTIONS]
Positional:
BASE Path to base model (.safetensors)
MODELS Path(s) to model files, or a directory
Options:
--arch ARCH sd-mecha architecture config (required)
--fix Auto-fix incompatible models in-place
--rank-samples N Number of keys for SVD analysis (default: 5)| Problem | Cause | Solution |
|---|---|---|
ModuleNotFoundError: sd_mecha |
sd-mecha not installed | pip install sd-mecha |
ModuleNotFoundError: OrthoMerge |
OrthoMerge.py not found | Copy to working directory or add to PYTHONPATH |
❌ Base nicht gefunden |
Wrong directory or filename | Check path in top bar, click 🔄 to refresh |
❌ Zu wenige Modelle |
Less than 2 compatible models | Ensure models match the base architecture |
| Out of memory during analysis | Too many large models | Reduce rank-samples or analyze fewer models |
| Merge timeout (> 4h) | Very large models or slow disk | Use SSD, reduce number of deltas |
| ComfyUI preview not working | ComfyUI not running | Start ComfyUI first, check API endpoint |
| NaN in merged model | Numerical instability | Post-merge fix handles this automatically |
- SSD storage dramatically speeds up streaming analysis (many random reads)
- rank-samples = 3 is usually sufficient; increase to 10 only for detailed analysis
- Fewer models = faster analysis (O(N²) pairwise comparisons)
- The streaming pass is the bottleneck — subsequent optimization is near-instant
Contributions are welcome! Areas of particular interest:
- New architectures — SD3, AuraFlow, Stable Cascade, etc.
- Better clustering heuristics — improved Realism vs. Utility separation
- Perceptual quality metrics — FID/CLIP-score integration for result evaluation
- Block-level alpha control — different alpha per transformer block
- Memory optimization — support for very large models (> 20 GB)
- Batch processing — analyze and merge multiple recipes in sequence
- Preset system — save and load mixer configurations
git clone https://github.com/YOUR_USERNAME/orthomerge-studio.git
cd orthomerge-studio
pip install -e ".[dev]" # If setup.py exists
# or simply:
pip install gradio safetensors torch| File | Lines | Responsibility |
|---|---|---|
analyze_deltas_v5.py |
~800 | All analysis, optimization, and script generation |
ortho_studio_v5.py |
~500 | Gradio UI, ComfyUI integration, merge execution |
The backend (analyze_deltas_v5.py) is fully independent of the GUI and can be used as a library or CLI tool.
- sd-mecha — Model merging framework that handles the actual weight manipulation
- OrthoMerge — Orthogonal merge algorithm preserving unique delta contributions
- ComfyUI — Node-based image generation backend used for previews
- Gradio — Web UI framework powering the interactive interface
- safetensors — Fast and safe model file format
This project is licensed under the MIT License
MIT License
Copyright (c) 2025 OrthoMerge Studio Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Built for the model merging community. If this tool helps you create better models, consider sharing your merge recipes!