Extending MergeKit with Custom Merge Methods
May 10, 2025 ยท View on GitHub
Overview
MergeKit offers two different paths for implementing custom merge methods:
| Decorator API | Class-based API | |
|---|---|---|
| Complexity | Simple function-based | Full class implementation |
| Abstraction Level | Higher-level | Lower-level |
| Parameter Handling | Automatic validation | Manual configuration |
| Execution Flow | Single function | Arbitrary computation graph |
| Best For | Most merge methods | Complex multi-stage, multi-input strategies |
Either approach benefits from MergeKit's underlying task system for resource management and execution control. The question of which to use largely depends on the complexity of the merge operation and the level of control needed.
Note on Parameter Configuration: MergeKit uses a hierarchical YAML-based configuration system. Parameters for your custom merge methods (both scalar and per-model) can be defined at various levels (e.g., globally, per-model, per-slice). The values your merge function or task receives are resolved by MergeKit based on this hierarchy and context. For full details on configuration structure and parameter precedence, please refer to the Merge Configuration section of the README.
Core Task System Features
MergeKit's computational graph infrastructure provides sophisticated resource management that all merge methods inherit:
-
Smart Memory Management
- Automatic return value lifecycle tracking
- Early value eviction when no longer needed
- Optimized shard loading based on task groups
-
Device Management
- Automatic tensor movement between compute and storage devices
- Support for both CPU and GPU execution
-
Task Scheduling
- Tasks grouped by tensor shard to minimize memory usage
- Loads deferred until last possible moment (via priority system)
- Execution ordered to optimize shard residency
Decorator API
Best for straightforward merge operations that can be expressed as a single tensor transformation. Features:
- Parameter validation, type checking, and value resolution
- Configuration schema generation
- Simplified base model handling
- Default GPU acceleration opt-in
Class-based API
Choose when you need:
- Multi-stage merge operations
- Custom computation graphs
- Direct access to weight metadata
- Complex parameter types
- Fine-grained control over execution
Decorator API Implementation
Basic Workflow
- Define a type-annotated Python function with your merge logic
- Add the
@merge_methoddecorator with configuration - Ensure the module containing your function is imported
- For MergeKit to discover your decorated merge method, the Python module containing it must be imported during MergeKit's initialization. Add an import statement for your module in
mergekit/merge_methods/__init__.py. Once the module is imported, the@merge_methoddecorator handles the registration of the method with MergeKit.
- For MergeKit to discover your decorated merge method, the Python module containing it must be imported during MergeKit's initialization. Add an import statement for your module in
Example: Weighted Average
from mergekit.merge_methods.easy_define import merge_method
from typing import List
import torch
@merge_method(
name="weighted_average",
pretty_name="Weighted Average", # Optional: human-readable name
reference_url="https://example.com/docs", # Optional: documentation or paper link
)
def average_merge(
tensors: List[torch.Tensor], # Required: input tensors
weight: List[float], # Vector parameter (one float per model)
normalize: bool = True, # Scalar parameter with default
) -> torch.Tensor:
if normalize:
total = sum(weight)
weight = [w / total for w in weight]
return sum(t * w for t, w in zip(tensors, weight))
This enables configurations like:
merge_method: weighted_average
models:
- model: model1
parameters:
weight: 0.3
- model: model2
parameters:
weight: 0.7
parameters: # Global parameters
normalize: true
Parameter Types and Handling
The decorator supports three parameter categories:
-
Scalar Parameters
- Types:
bool,float, orint - Single value for all models
- Without defaults they become required parameters
- Example:
normalize: bool = True
- Types:
-
Vector Parameters
- Types:
List[float]orList[int]only - Configured per-model
- Default values must be single numbers, not lists, as they are broadcasted
- Example:
weights: List[float]
- Types:
-
Base Model Integration The
tensors: List[torch.Tensor]argument and an optionalbase_tensorargument in your function signature interact with thebase_modelspecified in the YAML configuration as follows:- If your function includes a
base_tensorparameter (e.g.,base_tensor: torch.Tensororbase_tensor: Optional[torch.Tensor]):- The
base_tensorargument will receive the tensor from thebase_modelspecified in the YAML. If annotated asOptionaland nobase_modelis configured, it will beNone. - The
tensors: List[torch.Tensor]argument will only contain tensors from the models specified under themodels:key in the YAML, in order. It will not include the base model's tensor.
- The
- If your function does not include a
base_tensorparameter:- If a
base_modelis specified in the YAML, its tensor will be the first element in thetensors: List[torch.Tensor]list (i.e.,tensors[0]). - Subsequent elements (
tensors[1:]) will correspond to the models listed under themodels:key in the YAML, in order. - If no
base_modelis specified, thetensorslist will directly correspond to the models listed under themodels:key.
- If a
- If your function includes a
-
Special Auto-Populated Parameters Certain parameter names in your function signature have special meaning and are auto-populated by MergeKit if present. You do not configure these directly in the YAML
parameterssections for your method; MergeKit provides them.output_weight: WeightInfo: If your function accepts an argument namedoutput_weightannotated withWeightInfo, MergeKit will pass metadata about the specific weight tensor being computed.base_model: ModelReference(orOptional[ModelReference]): If your function acceptsbase_modelannotated withModelReference, MergeKit will pass a reference to the base model if one is used in the configuration for this merge operation.
Class-based API Implementation
For complex merges requiring granular control, implement MergeMethod and Task classes:
Example Implementation
from mergekit.merge_methods.base import MergeMethod, ConfigParameterDef
from mergekit.common import ImmutableMap, ModelReference, WeightInfo
from mergekit.graph import Task
from typing import Any, Dict, List
import torch
class CustomDependencyTask(Task[float]):
totally_real_parameter: str
# Example of a task that computes a dependency for the merge
def execute(self) -> float:
# Custom logic to compute a dependency
return 42.0
class CustomMergeTask(Task[torch.Tensor]):
gather_tensors: MergeTensorInput
parameters: ImmutableMap[str, Any]
tensor_parameters: ImmutableMap[ModelReference, ImmutableMap[str, Any]]
weight_info: WeightInfo
def arguments(self) -> Dict[str, Task]:
return {
"tensors": self.gather_tensors,
"dependency": CustomDependencyTask(totally_real_parameter="example"),
}
def priority(self) -> int:
return 1 # Optional: higher priority = earlier execution
def group_label(self) -> str:
return self.weight_info.name # Optional: modify task grouping
def uses_accelerator(self) -> bool:
return True # Enable GPU acceleration
def execute(
self, tensors: Dict[ModelReference, torch.Tensor], dependency: float
) -> torch.Tensor:
# Implementation using self.weight_info, self.parameters, and self.tensor_parameters
# Access global parameters via self.parameters["param_name"]
# Access per-model tensor parameters via self.tensor_parameters[model_ref]["tensor_param_name"]
# These values are pre-resolved by MergeKit's configuration system.
result = ...
return result
class CustomMerge(MergeMethod):
def name(self) -> str:
return "custom_merge"
def pretty_name(self) -> str:
return "Custom Merge"
def reference_url(self) -> str:
return "https://example.com/custom"
def parameters(self) -> List[ConfigParameterDef]:
return [
ConfigParameterDef("threshold", float, required=False, default_value=0.5)
]
def tensor_parameters(self) -> List[ConfigParameterDef]:
return [ConfigParameterDef("weight", float, required=True)]
def make_task(
self,
*,
output_weight: WeightInfo, # Metadata about the weight being computed
tensors: MergeTensorInput, # Internal Task that fetches input tensors
parameters: ImmutableMap[str, Any], # Global parameters
tensor_parameters: ImmutableMap[ModelReference, ImmutableMap[str, Any]], # Per-model parameters
**kwargs, # Other context like base_model: Optional[ModelReference]
) -> Task:
return CustomMergeTask(
gather_tensors=tensors,
parameters=parameters,
tensor_parameters=tensor_parameters,
weight_info=output_weight,
)
Task Scheduling System
The class-based API provides fine-grained control over execution:
- Priority Control: Override
priority()to influence execution order within groups - Task Grouping: Use
group_label()to batch similar operations - Resource Management:
- Automatic tensor lifecycle tracking
- Memory optimization via early tensor eviction
- Smart device placement for computation vs storage
- Computation Graph: Build complex flows by connecting multiple tasks
Implementation Requirements
-
Task Class:
- Must implement
execute()with proper type annotations - Must implement
arguments()to declare dependencies - Optionally override
priority(),group_label(),uses_accelerator()
- Must implement
-
Method Class:
- Must implement core methods:
name(),make_task() - Optional methods:
pretty_name(),reference_url() - Define parameters via
parameters()andtensor_parameters()
- Must implement core methods:
Registration
Add class-based methods to STATIC_MERGE_METHODS in mergekit/merge_methods/registry.py:
from mergekit.merge_methods.my_module import CustomMerge
STATIC_MERGE_METHODS: List[MergeMethod] = [
CustomMerge(),
# other methods...
]
Reference Implementations
-
Linear Merge (
mergekit.merge_methods.linear):- Basic weighted averaging
- Good example of class-based implementation
-
Multi-SLERP (
mergekit.merge_methods.multislerp):- Hypersphere interpolation
- Complex decorator usage example
-
Task Arithmetic (
mergekit.merge_methods.task_arithmetic):- Advanced graph-based implementation
- TIES/Magnitude pruning example