Model Merging
모델 병합은 하나의 기반 모델에서 파인튜닝한 여러 모델의 가중치를 추가 추론 비용 없이 단일 모델로 융합합니다. 평균화와 태스크 산술을 다룹니다.
모델 병합은 학습된 모델 두 개 이상을 하나의 모델로 결합하는 방식으로, 일반적으로 파라미터를 평균하거나 더하여 여러 네트워크를 실행하거나 모든 데이터로 재학습하지 않고도 각 모델의 능력을 결과 모델에 유지합니다. 모델들이 동일한 아키텍처를 공유하고, 대부분의 방법에서는 공통 사전 학습 시작점을 사용할 때 작동합니다. 파인튜닝은 각 사본을 가중치 공간에서 짧은 거리만큼 이동시키므로 모델 가중치가 파라미터 단위로 계속 일치합니다. 모델 병합은 오픈 대규모 언어 모델(LLM)을 구축하고 LoRA 어댑터나 Stable Diffusion 체크포인트를 결합하는 데 널리 사용됩니다. Meta의 Llama 3는 각 단계에서 서로 다른 데이터나 하이퍼파라미터로 얻은 모델을 사후 학습 과정에서 평균했습니다.
모델 병합의 작동 방식#
병합 방법은 결합하는 대상과 모델 간 충돌을 해결하는 방식에 따라 다릅니다.
- 모델 수프: 동일한 기본 모델에서 서로 다른 하이퍼파라미터로 파인튜닝한 모델의 가중치를 평균합니다. 파인튜닝된 ViT-G 모델을 탐욕적으로 병합한 수프는 ImageNet에서 90.94%의 top-1 정확도를 달성했으며, 당시 최첨단 결과였습니다.
- 확률적 가중치 평균: 한 번의 학습 실행 중 서로 다른 시점에 저장된 체크포인트를 평균하여, 손실 표면의 더 넓고 평탄한 영역에 안착하는 경향을 보입니다.
- 태스크 산술: 각 파인튜닝 모델에서 기본 가중치를 빼 태스크 벡터를 구한 다음, 스킬을 결합하려면 스케일 조정한 태스크 벡터를 기본 모델에 더하고 동작을 제거하려면 태스크 벡터 하나를 뺍니다.
- TIES-Merging: 세 단계로 태스크 벡터 간 간섭을 줄입니다. 파인튜닝 중 변화가 미미한 파라미터를 초기화하고, 각 파라미터의 부호를 정한 뒤, 해당 부호와 일치하는 값만 병합합니다.
- DARE: 각 태스크 벡터 값의 대부분을 무작위로 제거하고 나머지 값을 재조정한 뒤 병합하여, 각 태스크의 능력은 유지하면서 중복된 변화를 제거합니다.
한 번의 학습 실행 중 가중치를 평균하는 방식은 일반적인 학습에도 포함됩니다. Ultralytics YOLO26 학습에서는 최적화 중인 가중치와 함께 모델 가중치의 지수 이동 평균(EMA)을 유지하며, 학습이 진행됨에 따라 감쇠율이 0.9999에 가까워지도록 높입니다. 저장되는 last.pt 및 best.pt 체크포인트에는 이 평균 모델이 포함됩니다.
태스크 벡터 방식은 공통된 구조를 따릅니다. 모든 파인튜닝 모델을 기본 모델과의 차이로 표현하고, 차이를 정제해 결합한 뒤, 결과를 기본 모델에 다시 더합니다.
mergekit과 같은 오픈소스 도구는 LLM용 이러한 방법과 두 모델 간 구면 선형 보간(SLERP)을 구현합니다.
공통 시작점이 중요한 이유#
신경망의 은닉 유닛은 네트워크가 계산하는 결과를 바꾸지 않고도 순서를 바꿀 수 있습니다. 따라서 서로 다른 무작위 초기화로 학습된 두 네트워크는 같은 특징을 같은 위치에 저장하는 경우가 드물며, 두 모델을 평균하면 관련 없는 가중치가 섞입니다. 하나의 기본 모델에서 파인튜닝한 모델은 기본 모델의 배열을 유지하고 낮은 손실 영역에 머물기 때문에 평균을 내도 계속 작동합니다. 이 예제에서는 필기 숫자로 작은 PyTorch 분류기를 학습하고, 남은 학습 데이터의 서로 다른 절반으로 두 사본을 파인튜닝한 다음, 두 사본의 병합 결과와 서로 다른 초기화로 학습한 두 모델의 병합 결과를 비교합니다.
import copy
import torch
from sklearn.datasets import load_digits
from torch import nn
torch.manual_seed(0)
X, y = load_digits(return_X_y=True)
order = torch.randperm(len(X))
X, y = torch.tensor(X / 16, dtype=torch.float32)[order], torch.tensor(y)[order]
def mlp():
"""Return a small classifier for 8x8 digit images."""
return nn.Sequential(nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 10))
def train(model, rows, steps):
"""Train a model with SGD on the given rows and return it."""
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
for _ in range(steps):
optimizer.zero_grad()
nn.functional.cross_entropy(model(X[rows]), y[rows]).backward()
optimizer.step()
return model
base = train(mlp(), slice(0, 300), 400) # shared pretrained starting point
halves = (slice(300, 900), slice(900, 1500)) # two different fine-tuning datasets
for name, (a, b) in [
("fine-tuned from one base", [train(copy.deepcopy(base), rows, 100) for rows in halves]),
("trained from separate inits", [train(mlp(), rows, 400) for rows in halves]),
]:
# Merge: average every weight tensor of the two models
merged = copy.deepcopy(a)
merged.load_state_dict({k: (a.state_dict()[k] + b.state_dict()[k]) / 2 for k in a.state_dict()})
accuracy = [(m(X[1500:]).argmax(1) == y[1500:]).float().mean().item() for m in (a, b, merged)]
print(f"{name}: {accuracy[0]:.3f} and {accuracy[1]:.3f}, merged {accuracy[2]:.3f}")보류된 숫자 297개에서 하나의 기본 모델로 파인튜닝한 두 모델은 각각 0.956과 0.946을 기록하고, 두 모델의 평균은 0.970으로 어느 모델보다 높습니다. 각 모델이 데이터의 서로 다른 절반에서 학습한 내용을 결합하기 때문입니다. 서로 다른 초기화로 학습한 두 모델은 0.946과 0.956을 기록하지만, 평균은 0.808로 하락합니다. Git Re-Basin은 한 모델의 은닉 유닛을 다른 모델에 맞게 먼저 재배열하여 독립적으로 학습된 모델을 병합합니다.
모델 병합과 관련 용어 비교#
- 모델 앙상블: 모든 모델을 유지하고 출력을 결합하므로 모델 수가 늘어날수록 메모리 사용량과 추론 지연 시간이 증가합니다. 병합 모델은 모델 하나의 크기와 속도를 유지합니다.
- 지식 증류: 새로운 학생 모델이 하나 이상의 교사를 모방하도록 학습하며, 이 과정에는 학습 데이터와 연산 자원이 필요합니다. 병합은 기존 가중치에 직접 산술 연산을 적용합니다.
- 멀티태스크 학습: 모든 태스크의 데이터를 함께 사용해 모델 하나를 학습하며, 일반적으로 병합이 해당 데이터 없이 도달하려는 정확도 기준을 설정합니다.
- 연합 학습: 각 라운드마다 개별 기기에서 학습된 모델의 가중치를 평균합니다. 매 라운드는 모든 클라이언트가 동일한 글로벌 모델, 즉 평균이 제대로 작동하도록 하는 공통 시작점에서 시작합니다.










