본문으로 건너뛰기

[SGLang] Model Loader: 가중치 로딩 인프라와 최적화

들어가며

LLM 서빙에서 모델 로딩은 서버 시작 시간의 대부분을 차지한다. 수십 GB의 가중치를 디스크에서 GPU로 효율적으로 전송하는 것은 시스템 성능의 핵심이다. SGLang은 Safetensors 멀티스레드 로딩, 메모리 매핑, 텐서 병렬 분산 등 다양한 최적화를 제공한다.

이 글에서는 python/sglang/srt/model_loader/loader.pyweight_utils.py를 중심으로 Model Loader의 설계를 분석한다.

로더 계층 구조

SGLang은 다양한 로딩 형식을 지원하기 위해 추상 기반 클래스를 사용한다.

┌────────────────────┐
│  BaseModelLoader   │  (추상 기반)
├────────────────────┤
│  download_model()  │
│  load_model()      │
└────────┬───────────┘
         │
    ┌────┴──────────────────────┐
    │                           │
┌──────────────────┐  ┌─────────────────────┐
│ DefaultModelLoader│  │ DummyModelLoader    │
│ (Safetensors/PT) │  │ (테스트용 더미 가중치)│
└──────────────────┘  └─────────────────────┘
class BaseModelLoader(ABC):
    def __init__(self, load_config: LoadConfig):
        self.load_config = load_config

    @abstractmethod
    def download_model(self, model_config: ModelConfig) -> None:
        raise NotImplementedError

    @abstractmethod
    def load_model(self, *, model_config, device_config) -> nn.Module:
        raise NotImplementedError

DefaultModelLoader: 가중치 준비

DefaultModelLoader는 모델 가중치 파일을 찾고, 적절한 이터레이터를 선택한다.

가중치 파일 탐색

_prepare_weights()는 로컬 또는 HuggingFace Hub에서 가중치 파일을 찾는다.

def _prepare_weights(self, model_name_or_path, revision, fall_back_to_pt):
    is_local = os.path.isdir(model_name_or_path)
    load_format = self.load_config.load_format

    if load_format == LoadFormat.AUTO:
        allow_patterns = ["*.safetensors", "*.bin"]
    elif load_format == LoadFormat.SAFETENSORS:
        use_safetensors = True
        allow_patterns = ["*.safetensors"]
    elif load_format == LoadFormat.PT:
        allow_patterns = ["*.pt"]

    if not is_local:
        hf_folder = download_weights_from_hf(
            model_name_or_path, self.load_config.download_dir,
            allow_patterns, revision,
        )
    else:
        hf_folder = model_name_or_path

    return hf_folder, hf_weights_files, use_safetensors

Safetensors를 우선 사용하되, .pt 파일로 폴백할 수 있다.

가중치 이터레이터 선택

_get_weights_iterator()에서 포맷에 맞는 이터레이터를 선택한다.

def _get_weights_iterator(self, source):
    hf_folder, hf_weights_files, use_safetensors = self._prepare_weights(
        source.model_or_path, source.revision, source.fall_back_to_pt
    )

    if use_safetensors:
        if self.load_config.load_format == LoadFormat.FASTSAFETENSORS:
            weights_iterator = fastsafetensors_weights_iterator(hf_weights_files)
        elif use_multithread:
            weights_iterator = buffered_multi_thread_safetensors_weights_iterator(
                hf_weights_files,
                max_workers=extra_config.get("num_threads", self.DEFAULT_NUM_THREADS),
                disable_mmap=weight_loader_disable_mmap,
            )
        else:
            weights_iterator = safetensors_weights_iterator(
                hf_weights_files, disable_mmap=weight_loader_disable_mmap
            )
    else:
        weights_iterator = pt_weights_iterator(hf_weights_files)

기본 스레드 수는 8개이다.

class DefaultModelLoader(BaseModelLoader):
    DEFAULT_NUM_THREADS = 8

양자화 설정 로딩

모델 로딩 전에 양자화 설정을 확인한다.

def _get_quantization_config(model_config, load_config):
    model_class, _ = get_model_architecture(model_config)
    packed_modules_mapping = getattr(model_class, "packed_modules_mapping", {})

    if model_config.quantization is not None:
        quant_config = get_quant_config(
            model_config, load_config, packed_modules_mapping
        )
        # GPU Capability 검증
        major, minor = get_device_capability()
        capability = major * 10 + minor
        if capability < quant_config.get_min_capability():
            raise ValueError(
                f"The quantization method {model_config.quantization} "
                "is not supported for the current GPU."
            )
        return quant_config
    return None

device_loading_context: CPU ↔ GPU 전환

가중치를 CPU에서 로딩한 후 GPU로 옮기는 컨텍스트 매니저이다.

@contextmanager
def device_loading_context(module, target_device):
    if target_device.type == "cpu":
        yield module
        return

    original_infos = {}
    # CPU 파라미터를 GPU로 이동
    for name, p in module.named_parameters():
        if p.device.type == "cpu":
            device_data = p.data.to(target_device)
            original_infos[name] = dict(
                device=p.device,
                original_data=p.data,
                device_data=device_data,
            )
            p.data = device_data

    try:
        yield module
    finally:
        # 원래 디바이스로 복원
        for name, p in module.named_parameters():
            if name in original_infos:
                original_data = original_infos[name]["original_data"]
                original_data.copy_(p.data.to(original_data.device))
                p.data = original_data

weight_utils.py: 저수준 유틸리티

파일 잠금

여러 프로세스가 동시에 같은 모델을 다운로드하는 것을 방지한다.

def get_lock(model_name_or_path, cache_dir=None, suffix=""):
    lock_dir = cache_dir or temp_dir
    model_name = model_name_or_path.replace("/", "-")
    hash_name = hashlib.sha256(model_name.encode()).hexdigest()
    lock_file_name = hash_name + model_name + suffix + ".lock"
    lock = filelock.FileLock(os.path.join(lock_dir, lock_file_name), mode=0o666)
    return lock

mode=0o666으로 여러 사용자가 잠금을 공유할 수 있다.

Safetensors 중복 파일 필터링

분할 Safetensors 파일과 통합 파일이 공존하는 경우 충돌을 방지한다.

hf_weights_files = filter_duplicate_safetensors_files(
    hf_weights_files, hf_folder, index_file
)

MTP 가중치 필터링

Multi-Token Prediction 모델에서 특정 draft layer만 로딩한다.

@classmethod
def _filter_mtp_weights(cls, weights_iterator, prefix, draft_model_idx):
    filtered_weights = []
    for name, tensor in weights_iterator:
        match = cls._MTP_PATTERN.match(name)
        if match is not None:
            idx = int(match.group(1))
            if idx != draft_model_idx:
                continue
            new_name = name.replace(match.group(), "model.mtp.layers.0.")
        else:
            new_name = name
        filtered_weights.append((prefix + new_name, tensor))
    return tuple(filtered_weights)

로딩 형식 요약

SGLang이 지원하는 로딩 형식을 정리한다.

형식 파일 패턴 특징
AUTO *.safetensors, *.bin 자동 감지
SAFETENSORS *.safetensors 메모리 매핑, 빠른 로딩
FASTSAFETENSORS *.safetensors fastsafetensors 라이브러리
PT *.pt PyTorch 기본 형식
NPCACHE *.bin NumPy 캐시
MISTRAL consolidated*.safetensors Mistral 모델 전용
DUMMY - 테스트용 랜덤 가중치

설계 근거: 멀티스레드 로딩

가중치 로딩은 I/O 바운드이므로 멀티스레드가 효과적이다. buffered_multi_thread_safetensors_weights_iterator는 여러 스레드가 파일을 병렬로 읽고, 버퍼에 텐서를 쌓아 순차 소비한다.

Thread 1 ─→ file_1.safetensors ─→ ┐
Thread 2 ─→ file_2.safetensors ─→ ├→ Buffer ─→ weight_loader
Thread 3 ─→ file_3.safetensors ─→ ┘

관련 포스트

참고

댓글

관련 포스트

SGLang 의 다른글