merge_cp_2 / docs /create_a_merge_method.md
KwangHwi's picture
Add files using upload-large-folder tool
2480751 verified
|
Raw
History Blame Contribute Delete
11.5 kB
# Extending MergeKit with Custom Merge Methods
## 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](../README.md#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
1. Define a type-annotated Python function with your merge logic
2. Add the `@merge_method` decorator with configuration
3. 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_method` decorator handles the registration of the method with MergeKit.
### Example: Weighted Average
```python
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:
```yaml
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:
1. **Scalar Parameters**
- Types: `bool`, `float`, or `int`
- Single value for all models
- Without defaults they become required parameters
- Example: `normalize: bool = True`
2. **Vector Parameters**
- Types: `List[float]` or `List[int]` only
- Configured per-model
- Default values must be single numbers, not lists, as they are broadcasted
- Example: `weights: List[float]`
3. **Base Model Integration**
The `tensors: List[torch.Tensor]` argument and an optional `base_tensor` argument in your function signature interact with the `base_model` specified in the YAML configuration as follows:
- **If your function includes a `base_tensor` parameter (e.g., `base_tensor: torch.Tensor` or `base_tensor: Optional[torch.Tensor]`):**
- The `base_tensor` argument will receive the tensor from the `base_model` specified in the YAML. If annotated as `Optional` and no `base_model` is configured, it will be `None`.
- The `tensors: List[torch.Tensor]` argument will *only* contain tensors from the models specified under the `models:` key in the YAML, in order. It will *not* include the base model's tensor.
- **If your function does *not* include a `base_tensor` parameter:**
- If a `base_model` is specified in the YAML, its tensor will be the *first element* in the `tensors: List[torch.Tensor]` list (i.e., `tensors[0]`).
- Subsequent elements (`tensors[1:]`) will correspond to the models listed under the `models:` key in the YAML, in order.
- If no `base_model` is specified, the `tensors` list will directly correspond to the models listed under the `models:` key.
4. **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 `parameters` sections for your method; MergeKit provides them.
- `output_weight: WeightInfo`: If your function accepts an argument named `output_weight` annotated with `WeightInfo`, MergeKit will pass metadata about the specific weight tensor being computed.
- `base_model: ModelReference` (or `Optional[ModelReference]`): If your function accepts `base_model` annotated with `ModelReference`, 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
```python
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
1. Task Class:
- Must implement `execute()` with proper type annotations
- Must implement `arguments()` to declare dependencies
- Optionally override `priority()`, `group_label()`, `uses_accelerator()`
2. Method Class:
- Must implement core methods: `name()`, `make_task()`
- Optional methods: `pretty_name()`, `reference_url()`
- Define parameters via `parameters()` and `tensor_parameters()`
### Registration
Add class-based methods to `STATIC_MERGE_METHODS` in `mergekit/merge_methods/registry.py`:
```python
from mergekit.merge_methods.my_module import CustomMerge
STATIC_MERGE_METHODS: List[MergeMethod] = [
CustomMerge(),
# other methods...
]
```
## Reference Implementations
1. **Linear Merge** (`mergekit.merge_methods.linear`):
- Basic weighted averaging
- Good example of class-based implementation
2. **Multi-SLERP** (`mergekit.merge_methods.multislerp`):
- Hypersphere interpolation
- Complex decorator usage example
3. **Task Arithmetic** (`mergekit.merge_methods.task_arithmetic`):
- Advanced graph-based implementation
- TIES/Magnitude pruning example