Transformers documentation

동적 가중치 로드

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

동적 가중치 로드

Checkpoints는 모델이 런타임에 기대하는 형식과 일치하지 않는 형식으로 직렬화되는 경우가 많습니다. 일반적인 시나리오에는 다음이 포함됩니다.

  1. 병합된 가중치: 체크포인트에는 gate_proj와 up_proj 가중치가 별도로 저장되지만, 모델은 효율성을 위해 병합된 gate_up_proj를 사용합니다.
  2. MoE 전문가 통합: 개별 전문가 가중치(experts.0.weight, experts.1.weight, …)를 하나의 3D 텐서로 스택해야 합니다.
  3. 레거시 네이밍: 이전 체크포인트는 다른 네이밍 규칙(예: LayerNorm.gamma과 LayerNorm.weight)을 사용합니다.
  4. 복합 모델: 비전-언어 모델에는 각자 고유한 체크포인트 규약을 가진 두 개의 PreTrainedModel 서브모듈이 포함됩니다.
  5. Quantization: 가중치가 역직렬화가 필요한 양자화 형식으로 저장되어 있을 수 있습니다.

Dynamic weight loading은 체크포인트 텐서가 로드될 때 예약된 가역 연산을 적용하여 이 문제를 해결합니다. Transformers는 WeightConverter와 WeightRenaming을 통해 이를 제공하며, 이는 하나 이상의 체크포인트 키가 하나 이상의 모델 파라미터에 어떻게 매핑되는지, 그리고 일치하는 텐서에 실행해야 하는 구성 가능한 ConversionOps가 무엇인지를 설명합니다. 이 접근 방식은 새로운 가중치 레이아웃에 적응하고, 양자화된 mixture-of-experts(MoE)를 지원하며, 텐서 병렬ism과 통합됩니다.

이 가이드에서는 WeightConverter를 사용하여 텐서를 변환하는 방법을 보여줍니다. 변환 매핑은 conversion_mapping.py에 있으며, 등록된 매핑은 model_type 문자열(예: "mixtral") 또는 클래스 이름(예: "LlavaModel")을 키로 사용합니다.

전체 로딩 파이프라인

모든 모델은 동적 가중치 로딩 시스템을 거칩니다. 변환 매핑은 해당 시스템 내에서 모델의 클래스 이름 또는 model_type에 대해 항목이 등록되어 있을 때만 활성화되는 선택 단계입니다.

Checkpoint File → from_pretrained() → convert_and_load_state_dict_in_model()
                                              ↓
                         ┌───────────────────────────────────────────────────────────┐
                         │  For each weight in checkpoint:                           │
                         │  1. Match renamed/processed source key to model parameter │
                         │  2. Shard the weight and send to device (async)           │
                         │  3. Collect tensors with the same source_pattern together │
                         │     (e.g. MoE experts, gate_up_proj)                      │
                         │  4. Apply dequantization/deserialization (if pre-quant)   │
                         │  5. Apply conversion (if defined)                         │
                         │  6. Apply quantization (if enabled and step 4 not used)   │
                         │  7. Set parameter on model                                │
                         └───────────────────────────────────────────────────────────┘
단계활성화될 때
동적 로딩항상, 모든 모델에 대해
변환 매핑모델의 클래스 또는 model_type이 _MODEL_TO_CONVERSION_PATTERN에 등록되어 있는 경우에만
TP 샤딩DistributedConfig(tp_size=N)이 사용되고 모델에 base_model_tp_plan이 있는 경우에만
Dequantization/deserializationpre-quantized 체크포인트를 로드할 때만
양자화quantization config가 제공되고 가중치가 사전 양자화되지 않은 경우에만

Dense 모델(예: Llama)

대부분의 dense 모델의 경우, 체크포인트 형식이 모델 형식과 직접적으로 일치하므로 변환 매핑이 필요하지 않습니다. 일부 모델은 여전히 이름 변경이 필요할 수 있습니다(예: 레거시 명명 규칙). TP 샤딩은 활성화된 경우에도 계속 적용됩니다.

Checkpoint:                             Model:
model.layers.0.self_attn.q_proj.weight  →  model.layers.0.self_attn.q_proj.weight
model.layers.0.self_attn.k_proj.weight  →  model.layers.0.self_attn.k_proj.weight
model.layers.0.mlp.gate_proj.weight     →  model.layers.0.mlp.gate_proj.weight
model.layers.0.mlp.up_proj.weight       →  model.layers.0.mlp.up_proj.weight
model.layers.0.mlp.down_proj.weight     →  model.layers.0.mlp.down_proj.weight

레거시 체크포인트는 모든 모델에 적용되는 내장 이름 변경 규칙으로 처리되는 구버전 명명 규칙을 사용할 수 있습니다:

Checkpoint:                          Model:
LayerNorm.gamma              →       LayerNorm.weight
LayerNorm.beta               →       LayerNorm.bias

MoE 모델(예: Mixtral)

MoE 모델의 경우, 체크포인트 형식이 모델 형식과 다릅니다. 변환 매핑은 개별 expert 가중치를 융합된 3D 텐서로 변환하며, TP 샤딩은 변환 후에 적용됩니다.

Checkpoint:                              Model:
experts.0.w1.weight  ─┐
experts.1.w1.weight   │ MergeModulelist
...                   ├───────────────→  experts.gate_up_proj (8, hidden, 2*intermediate)
experts.0.w3.weight   │ + Concatenate
experts.1.w3.weight  ─┘

복합 모델(예: 비전-언어)

PreTrainedModel에는 다른 PreTrainedModel 서브모듈이 포함될 수 있습니다. 각 서브모델은 클래스 이름 또는 model_type에 대해 등록된 자체 변환 매핑을 가질 수 있습니다. 상위 모델이 로드되면 get_model_conversion_mapping은 서브모델을 깊이 우선 순서로 탐색하고,它们的 매핑을 수집한 뒤, scope_prefix를 통해 각 변환을 해당 서브모듈의 경로에 자동으로 스코프를 지정합니다.

Composite model:                              Per-submodel mappings (auto-scoped):
LlavaForConditionalGeneration
├── vision_model: SiglipVisionModel    →     SiglipVisionModel mapping (scope="vision_model")
└── language_model: LlamaForCausalLM   →     LlamaForCausalLM mapping  (scope="language_model")

scope_prefix는 하위 모듈의 점으로 구분된 경로입니다("vision_model", "language_model.model" 등). 스코프가 지정된 변환은 f"{scope_prefix}."로 시작하는 키에서만 실행됩니다. 패턴 매칭 전에 접두사가 제거되고 치환 후 다시 붙여지므로, 각 하위 모델의 매핑은 루트인 것처럼 하위 모델 기준으로 작성됩니다.

아키텍처

시스템은 core_model_loading.py에 정의된 여러 핵심 구성 요소 중심으로 구축되어 있습니다:

1단계 — 키별 처리(체크포인트 키를 반복합니다):

  1. transform 목록을 한 번만 순회합니다. 일치하는 모든 WeightRenaming이 실행되며(예: block_sparse_moe → mlp), 최대 하나의 WeightConverter만 해당 키를 주장할 수 있습니다(예: experts.*.w1.weight).
  2. ThreadPoolExecutor를 통해 비동기적으로 Shard(TP) 및 디바이스 전송
  3. source_pattern이 동일한 Collect 텐서를 함께 묶습니다(예: 모든 MoE expert weights, gate + up projections).

2단계 — 매핑별 처리 (수집된 매핑을 반복합니다):

  1. Dequantize/deserialize(사전 양자화 체크포인트만 해당).
  2. ConversionOps 체인 적용: Chunk, Concatenate, MergeModulelist, Transpose 등
  3. 사전 양자화가 되어 있지 않은 경우, Quantize를 통해 실시간으로 양자화합니다.
  4. 모델에 매개변수 설정

WeightTransform

패턴 매칭과 텐서 수집을 처리하는 베이스 클래스:

  • 패턴 컴파일: 소스 패턴은 re.search()로 매칭되는 완전한 정규식입니다. * 와일드카드는 인덱싱 가능한 모든 구성 요소를 매칭하며, 모든 매칭 결과를 묶어 배치 연산을 수행합니다.
  • 캡처 그룹 및 백레퍼런스: 소스 패턴의 캡처 그룹은 대상 패턴에서 \1, \2, …으로 참조하여 이름 변경 과정에서 부분 문자열(예: 레이어 인덱스)을 유지할 수 있습니다.
  • 범위 지정: scope_prefix(get_model_conversion_mapping에 의해 서브 모델별로 자동으로 설정됨)은 변환을 해당 경로 아래 키로 제한합니다. 접두사는 매칭 전에 제거되고 치환 후 다시 붙여집니다.
  • 주요 이름 변경: rename_source_key()는 정규식(범위 처리 포함)을 적용하고 (renamed_key, source_pattern)을 반환하여, 로더가 해당 키를 어떤 변환기가(있다면) 주장했는지 알 수 있도록 합니다.
  • 텐서 컬렉션: add_tensor()는 source_pattern 아래에 해결된 텐서(또는 Future)를 누적하여, 단일 변환(예: 모든 MoE expert weights)에 필요한 모든 텐서가 연산 체인이 실행되기 전에 함께 배치되도록 합니다.
  • 가역성: reverse_transform()은 소스 ↔ 대상 패턴을 교환하고 각 연산을 역전시키므로, 동일한 목록을 역순으로 사용하면 저장을 수행할 수 있습니다.
  • 추적: was_used()는 로딩 과정에서 변환이 실제로 어떤 키와도 매칭되었는지를 보고합니다. 이는 PrefixChange와 같이 이미 존재할 수 있는 접두사를 추가하는 비생성(non-bijective) 이름 변경이 저장 시 대칭적으로 다시 적용될 수 있도록 하는 데 필요합니다.

WeightRenaming

WeightRenaming은 텐서 연산 없이 순수한 키 이름 변경을 위한 전용 WeightTransform입니다. WeightConverter와 달리 WeightRenaming은 키를 claim하지 않으므로 여러 이름 변경이 자유롭게 체인될 수 있습니다. 로드 경로에서는 모든 이름 변경이 컨버터보다 먼저 실행되고, 저장 경로(역순 목록)에서는 모든 역전된 이름 변경이 역전된 컨버터보다 나중에 실행됩니다 — 위의 순서 규칙을 참조하세요.

# Legacy checkpoint compatibility
WeightRenaming("LayerNorm.gamma", "LayerNorm.weight")

# Module path changes
WeightRenaming(".block_sparse_moe.", ".mlp.")

PrefixChange는 전체 경로 요소를 제거하거나 추가하는 일반적인 경우를 위해 WeightRenaming을 감싸는 더 높은 수준의 래퍼입니다. 선택적 model_prefix는 해당 이름 공간 아래에 있는 키로 작업을 한정합니다:

# "model.layers.bad_prefix.weight" → "model.layers.weight"
PrefixChange(prefix_to_remove="bad_prefix", model_prefix="model.layers")
# "layers.0.weight" → "model.layers.0.weight"
PrefixChange(prefix_to_add="model")

WeightConverter

WeightConverter는 수집된 텐서에 적용되는 ConversionOps 체인으로 WeightTransform을 확장합니다. 지원되는 4가지 카디널리티는 다음과 같습니다:

Cardinality소스 패턴대상 패턴일반적인 동작
1:111Transpose, PermuteForRope
1:N1>1Chunk (예: qkv_proj 언팩)
다대일>11Concatenate, MergeModulelist (예: 전문가 병합)
다대다>1>1명시적으로 지원하는 연산(예: ErnieFuseAndSplitTextVisionExperts)에서만
WeightConverter(
    source_patterns=[".experts.*.w1.weight", ".experts.*.w3.weight"],
    target_patterns=".experts.gate_up_proj",
    operations=[MergeModulelist(dim=0), Concatenate(dim=1)],
)

WeightConverter는 가역적입니다. reverse_transform()은 source ↔ target를 교환하고 각 연산을 해당 reverse_op로 대체합니다. 따라서 등록된 변환 매핑은 구조상 양방향입니다(로딩 시에는 목록을 그대로 사용하고, 저장 시에는 반대로 사용합니다).

이름 변경 및 변환기 순서

WeightRenaming 항목을 WeightConverter 항목보다 앞에 배치하고, 각 항목의 리프(leaf)가 서로 겹치지 않도록 유지하세요. rename은 converter가 소비하는 키를 정규화하는 역할을 하지만, converter가 생성하는 리프를 대상으로 해서는 안 됩니다. 저장 경로는 이 규칙에 의존하여 매핑을 두 단계(먼저 converter 역순, 그다음 rename 역순)로 역전시킵니다.

transform 목록은 각 체크포인트 키마다 한 번씩 순서대로 순회됩니다. 각 키에 대해:

  • 일치하는 모든 WeightRenaming이 실행되며, 여러 이름 변경이 체인 방식으로 수행될 수 있습니다.
  • WeightConverter 중 claims로 해당 키를 주장하는 첫 번째 변환기가 선택됩니다. 이후의 변환기는 건너뜁니다. 이를 통해 텐서가 병합/분할 단계에서 단일 변환기로만 라우팅되도록 보장하며, 의도가 겹치는 두 변환기가 존재하는 것은 잘못된 구성임을 의미합니다.
weight_mapping = [
    WeightRenaming("^old_prefix", "encoder"),              # rename runs always
    WeightConverter(                                       # converter claims the key
        "attn.qkv_proj.weight",
        ["attn.q_proj.weight", "attn.k_proj.weight", "attn.v_proj.weight"],
        operations=[Chunk(dim=0)],
    ),
]
# Load:  "old_prefix.attn.qkv_proj.weight"
#   → WeightRenaming  → "encoder.attn.qkv_proj.weight"
#   → WeightConverter → "encoder.attn.{q,k,v}_proj.weight"
#
# Save: list reversed, each transform inverted:
#   → rev(WeightConverter) repacks QKV → "encoder.attn.qkv_proj.weight"
#   → rev(WeightRenaming)  fixes prefix → "old_prefix.attn.qkv_proj.weight"

변환 작업

WeightConverter 클래스에는 from_pretrained()가 호출될 때 체크포인트의 소스 텐서를 모델의 대상 텐서로 변환하기 위해 실행되는 여러 연산이 있습니다.

Operations은 완전히 가역적입니다. 저장 시 변환이 되돌려져 원래의 checkpoint가 반환되므로, 다양한 프레임워크를 오가며 작업하기가 쉽습니다.

작업역순
Chunk(dim)Concatenate(dim)
Concatenate(dim)Chunk(dim)
MergeModulelist(dim)SplitModulelist(dim)
SplitModulelist(dim)MergeModulelist(dim)
Transpose(d0, d1)Transpose(d1, d0)
PermuteForRope()PermuteForRope()
Conv3dToLinear(...)LinearToConv3d(...)

Chunk

Chunk 연산은 텐서를 특정 차원 방향으로 동일한 크기의 여러 부분으로 나눕니다. 예를 들어, 모델이 단일 텐서가 아닌 Q, K, V를 세 개의 개별 텐서로 기대하는 경우입니다.

WeightConverter(
    "self_attn.qkv_proj",
    ["self_attn.q_proj", "self_attn.k_proj", "self_attn.v_proj"],
    operations=[Chunk(dim=0)],
)

연결

Concatenate 연산은 개별 텐서를 하나의 텐서로 결합합니다. 예를 들어, 모델이 개별 텐서가 아닌 Q, K, V를 하나의 텐서로 기대하는 경우입니다.

WeightConverter(
    ["self_attn.q_proj", "self_attn.k_proj", "self_attn.v_proj"],
    "self_attn.qkv_proj",
    operations=[Concatenate(dim=0)],
)

MergeModulelist

MergeModulelist은 2D 텐서 목록을 하나의 3D 텐서로 병합합니다. 예를 들어, MergeModulelist을 Concatenate와 조합하여 MoE의 전문가를 스택하고 하나의 텐서로 패키징할 수 있습니다.

WeightConverter(
    ["block_sparse_moe.experts.*.w1.weight", "block_sparse_moe.experts.*.w3.weight"],
    "mlp.experts.gate_up_proj",
    operations=[MergeModulelist(dim=0), Concatenate(dim=1)],
)

SplitModulelist

SplitModulelist은 3D 텐서를 2D 텐서 목록으로 다시 분리합니다. 예를 들어, expert 스택을 개별 expert로 다시 분리할 수 있습니다.

WeightConverter(
    "mlp.experts.down_proj",
    "block_sparse_moe.experts.*.w2.weight",
    operations=[SplitModulelist(dim=0)],
)

PermuteForRope

PermuteForRope는 가중치를 인터리브드 형식에서 sin/cos 형식으로 변환합니다. 예를 들어, Chunk와 PermuteForRope를 조합하여 융합된 QKV 텐서를 분리하고 Q와 K에 sin/cos RoPE 순열을 적용할 수 있습니다.

WeightConverter(
    ["model.layers.*.self_attn.qkv_proj.weight"],
    [
        "model.layers.*.self_attn.q_proj.weight",
        "model.layers.*.self_attn.k_proj.weight",
        "model.layers.*.self_attn.v_proj.weight",
    ],
    operations=[Chunk(dim=0), PermuteForRope()],
)

전치

Transpose는 텐서의 차원을 교환합니다. 서로 다른 규약 간에 가중치 레이아웃을 변환하는 데 유용합니다.

WeightConverter(
    source_patterns="mlp.gate.weight",
    target_patterns="mlp.text_moe.gate.weight",
    operations=[Transpose(dim0=0, dim1=1)],
)

연산 체이닝

Operations를 체인하여 복잡한 변환을 수행할 수 있습니다. 각 연산은 순서대로 실행되며, 이전 연산의 출력이 다음 연산의 입력이 됩니다.

예: Mixtral MoE 변환

WeightConverter(
    source_patterns=[
        ".experts.*.w1.weight",  # gate_proj per expert
        ".experts.*.w3.weight",  # up_proj per expert
    ],
    target_patterns=".experts.gate_up_proj",
    operations=[
        MergeModulelist(dim=0),  # Stack all experts: (n_experts, in, out)
        Concatenate(dim=1),      # Fuse gate+up: (n_experts, in, 2*out)
    ],
)

데이터 흐름:

Input:
  ".experts.*.w1.weight": [tensor_0, tensor_1, ..., tensor_7]  # 8 experts
  ".experts.*.w3.weight": [tensor_0, tensor_1, ..., tensor_7]  # 8 experts

After MergeModulelist(dim=0):
  ".experts.*.w1.weight": (8, 4096, 14336)  # stacked gate
  ".experts.*.w3.weight": (8, 4096, 14336)  # stacked up

After Concatenate(dim=1):
  ".experts.gate_up_proj": (8, 4096, 28672)  # fused gate_up

패턴 매칭 규칙

  • Source patterns은 re.search()로 평가되는 완전한 정규식입니다. ^는 scope_prefix 제거 후의 scoped 키 시작 부분에 앵커링됩니다.
  • *은 인덱스별 와일드카드로, 동일한 소스 패턴 아래에서 모든 일치하는 텐서를 수집합니다(올바른 연결을 위해 체크포인트 순서를 유지).
  • 소스 패턴의 캡처 그룹은 \1, \2, …과 같은 형태로 대상 패턴에서 참조할 수 있으며, 이를 통해 원래 키의 일부(예: 레이어 인덱스)를 유지할 수 있습니다.
  • 범위 변환(scope_prefix이 설정된 경우)은 f"{scope_prefix}."로 시작하는 키만 일치시킵니다.

변환 매핑 등록

변환 목록은 문자열 키에 대해 등록됩니다. 이 키는 model_type(예: "mixtral") 또는 클래스 이름(예: "LlavaModel")입니다:

from transformers.conversion_mapping import register_checkpoint_conversion_mapping

register_checkpoint_conversion_mapping(
    "my_model_type",
    [
        WeightRenaming(".old.", ".new."),
        WeightConverter(
            ".experts.*.w1.weight",
            ".experts.gate_proj",
            operations=[MergeModulelist(dim=0)],
        ),
    ],
)

조회 규칙

get_model_conversion_mapping이 PreTrainedModel을 처리할 때, 모든 하위 PreTrainedModel은 DFS 순서로 방문됩니다(PreTrainedModel 인스턴스로 필터링된 nn.Module.named_modules()). 각 하위 모델은 두 단계로 해결되며, 먼저 사용자 정의 코드 필터가 실행되고, 이후 등록된 매핑이 조회됩니다.

커스텀 코드 모델은 조회가 수행되기 전에 건너뜁니다. is_custom_code()가 True를 반환하는 경우 해당 서브모델은 커스텀 코드로 간주되며, 이는 trust_remote_code=True로 Hub에서 로드된 모델과 Transformers에 없는 모든 모델 클래스를 포함합니다. 클래스 이름이나 model_type이 네이티브 모델과 충돌할 수 있으므로 커스텀 코드 모델은 변환 매핑을 상속하지 않습니다. 예를 들어, model_type="mixtral"을 설정하는 커스텀 아키텍처는 Mixtral의 내장 expert-fusion 변환을 적용받지 않습니다.

register_checkpoint_conversion_mapping를 사용하여 커스텀 코드 모델에 변환 매핑을 적용하려면 명시적으로 등록하세요. 등록 시 클래스 이름 또는 model_type이 사용자 등록 매핑 집합에 추가되며, 이를 통해 해당 모델은 커스텀 코드 스킵에서 제외되고 아래에 설명된 일반 조회 경로를 거치게 됩니다.

custom code 필터를 통과하는 각 서브 모델에 대해:

  1. 클래스 이름 조회가 먼저 시도됩니다. 그 다음 model_type가 조회됩니다. 두 항목이 모두 동일한 모듈에 등록되어 있는 경우, 클래스 이름 매핑이 우선 적용되고 해당 모듈에 대해서는 model_type 항목이 무시됩니다. 이를 통해 태스크 헤드(예: LlavaForConditionalGeneration)가 공유되는 model_type 기본값("llava")을 오버라이드할 수 있습니다.
  2. 선택된 매핑의 scope_prefix는 서브모듈의 점 경로(""는 루트)로 설정됩니다.
  3. Ancestor-based deduplication 매핑을 유지할지 여부를 결정합니다:
    • 조상 경로가 이미 동일한 식별자(클래스 이름 또는 model_type)를 사용 중이라면, 해당 하위 모듈은 건너뜁니다 — 조상의 범위가 없는 매핑 또는 상위 범위의 매핑이 이미 이 하위 트리를 포함하고 있기 때문입니다.
    • sibling만 해당 모듈을 claim한 경우, 해당 서브모듈은 자체 scope_prefix를 유지한 채 보관됩니다. 각 sibling은 자체 scoped mapping을 갖습니다.

클래스 이름과 model_type의 확인 목록(seen-list)은 별도로 추적되며, 한 가지 세부 사항이 있습니다. 모듈이 클래스 이름으로 일치할 경우, 해당 모듈의 model_type은 확인 목록에 추가되지 않습니다. 이는 동일한 model_type를 공유하지만 클래스별 매핑이 없는 다른 모듈(예: DetrForSegmentation 아래에 있는 DetrModel)이 model_type 조회를 통해 여전히 접근 가능하도록 하기 위함입니다.

클래스 기반 매핑과 model_type 별칭

_MODEL_TO_CONVERSION_PATTERN에는 두 가지 스타일이 공존합니다:

_MODEL_TO_CONVERSION_PATTERN = {
    # model_type aliases (lookup by config.model_type)
    "minimax": "mixtral",
    "qwen3_moe": "qwen2_moe",
    "mistral3": "llava",
    # class-name aliases (lookup by type(submodule).__name__)
    "PaliGemmaModel": "LlavaModel",
    "MaskFormerDetrDecoder": "DetrModel",
    ...
}

model_type가 공유되지만 특정 클래스에 다른 동작이 필요한 경우, 클래스 이름 키를 사용하는 것이 권장됩니다.

Tensor parallelism 통합

동적 로딩 시스템은 src/transformers/integrations/tensor_parallel.py에 정의된 TensorParallelLayer 계층 구조를 통해 텐서 병렬(TP)과 통합됩니다.

TP가 활성화되면 텐서는 물리화되는 동안 샤딩되며, 이후에 샤딩되지 않습니다. 이는 각 랭크가 필요한 텐서 부분만 로드한다는 것을 의미합니다.

def spawn_tp_materialize(thread_pool, tensor, sharding_method, tensor_idx, device, dtype):
    def _job():
        return sharding_method.shard_tensor(tensor, tensor_idx=tensor_idx, device=device, dtype=dtype)
    return thread_pool.submit(_job)

사용 가능한 병렬 스타일

스타일가중치 샤드 차원설명
colwise-2Column-wise: output features sharded
rowwise-1행 단위: 입력 특징이 샤드됨
packed_colwise-2융합된 가중치(gate_up_proj)의 경우
packed_rowwise-1융합된 가중치에 대해
embedding_rowwise0어휘 병렬
grouped_gemm0MoE의 전문가 병렬
sequence_parallelNoneNo weight sharding

Packed weight 처리

gate_up_proj와 같은 융합된 가중치의 경우, 올바르게 샤딩하려면 특별한 주의가 필요합니다:

def get_packed_weights(param, empty_param, device_mesh, rank, dim):
    """
    Interleaves gate and up shards correctly.

    Packed tensor: [G0 G1 G2 G3 | U0 U1 U2 U3]

    With TP=2:
    - Rank 0 gets: [G0 G1 | U0 U1]
    - Rank 1 gets: [G2 G3 | U2 U3]
    """

TP 연산은 WeightTransform에 저장되며 변환 연산 이후에 적용됩니다:

if matched_tp_pattern := tp_plan_alt.search(renamed_key):
    tp_layer = ALL_PARALLEL_STYLES[model.tp_plan[matched_tp_pattern]]
    mapping.distributed_operation = tp_layer(
        device_mesh=device_mesh,
        rank=device_mesh.get_local_rank(),
        empty_param=empty_param.clone()
    )

Quantization 통합

Quantization은 체크포인트가 이미 양자화되었는지에 따라 두 가지 방식으로 로딩 파이프라인에 통합됩니다:

  • 사전 양자화 체크포인트: 양자화기는 양자화 텐서를 역직렬화하는 WeightConverter 인스턴스(get_weight_conversions()를 통해)를 제공합니다. 원치 않는 캐스팅을 방지하기 위해 체크포인트의 dtype이 유지됩니다.
  • 온더플라이 양자화: 양자화기는 변환 연산 이후에 적용되는 양자화 작업을 제공하여, 가중치가 로드되는 시점에 가중치를 양자화합니다.

get_model_conversion_mapping의 끝에서 update_weight_conversions(...)을 통해 양자화기가 전체 변환 목록을 다시 작성할 수도 있습니다. 예를 들어, FP8 디양자화기는 모든 기존 컨버터 앞에 Fp8Dequantize 연산을 추가하여, per-expert 구조가 expert-merge / concat 연산에 의해 평탄화되기 전에 per-block scale이 적용되도록 합니다.

빠르고 효율적인 모델 로딩

로더가 연산에 필요한 텐서를 알고 지연 방식으로 materialization을 스케줄하기 때문에, 모델 로딩이 더 빠르고 메모리 사용량도 적습니다.

로더는 체크포인트를 한 번 스캔하여 패턴 매치를 발견하고 텐서를 수집합니다. 수집된 텐서는 Future 객체로 저장되며, GIL을 차단하지 않고 비동기 로딩을 위해 스레드 풀에 제출됩니다. 스레드가 사용 가능해지면 해당 파라미터의 로딩이 즉시 시작됩니다.

시스템에서 다른 무거운 프로세스가 실행 중이라면, 여러 스레드가 로딩을 가속하는 대신 오히려 지연시킬 수 있습니다. 이 경우, 가중치를 순차적으로 로드하려면 환경 변수 HF_DEACTIVATE_ASYNC_LOAD=1을 설정하세요.

기본값은 비동기 파라미터 로딩을 위한 4개의 스레드입니다. 이는 다양한 로딩 시나리오와 하드웨어에서 최적의 균형을 제공합니다. 작업은 주로 I/O에 의존하지만, 가속기 하드웨어와 로딩 시 필요한 dtype에 따라, dtype이 직렬화된 것과 다를 경우 CPU/GPU에 의존할 수 있습니다(이 경우 추가 복사 작업이 필요합니다).

비동기 및 동기 로딩

def spawn_materialize(thread_pool, tensor, device, dtype) -> Future | Callable:
    def _job():
        return _materialize_copy(tensor, device, dtype)

    if thread_pool is not None:
        return thread_pool.submit(_job)  # Async: returns Future
    else:
        return _job  # Sync: returns Callable (deferred execution)

동기 로딩은 다음 경우에 사용됩니다:

  • HF_DEACTIVATE_ASYNC_LOAD=1 환경 변수가 설정되어 있습니다.
  • disk offloading이 활성화되어 있습니다(메모리 제약으로 인해 순차적 로딩이 필요합니다).
  • On-the-fly 양자화가 활성화되어 있습니다(양자화 단계보다 워커 스레드가 앞서가는 것을 방지합니다).

물리화 플로우

1. Checkpoint iteration (Phase 1):
   - For each key, walk the transform list once
   - Submit materialization job to ThreadPoolExecutor
   - Job returns Future (async) or Callable (sync)
   - Collect into the matching WeightConverter / WeightRenaming

2. Per-mapping processing (Phase 2, one mapping at a time):
   - materialize_tensors() waits for this mapping's Futures only
   - Apply conversion operations chain (self.operations)
   - Apply quantization operation (if on-the-fly)
   - Set parameters on model
   - Delete realized tensors immediately

3. Cleanup:
   - Thread pool shutdown (with cancel_futures=True for interrupts)

메모리 효율

가중치를 변환할 때, 변환기는 아직 로드되지 않은 필수 텐서가 모두 materialize되기를 기다립니다. 예를 들어, MergeModulelist 연산은 병합 전에 ModuleList의 모든 가중치가 로드되어야 합니다.

텐서 연결은 임시 복사가 필요하므로, MergeModulelist와 Concatenate와 같은 연산은 변환 과정에서 기반 텐서의 2배에 해당하는 메모리가 필요합니다. 병합이 완료되면 결과 텐서만 메모리에 유지됩니다. 이론상 최악의 경우 메모리 피크는 모델 크기加上 가장 큰 MergeModulelist 또는 Concatenate 연산에 필요한 텐서 크기입니다.

이 최악의 상황은 다른 모든 파라미터가 요구되는 변환이 실행되기 전에 로드된 경우에만 발생합니다. 두 가지 시나리오에서 이 상황이 발생합니다.

  1. 요구되는 변환에 진입하기 전에 모든 파라미터가 비동기적으로 로드되었습니다(스레드 풀이 변환 큐보다 빨랐습니다).
  2. 가장 까다로운 변환은 마지막 단계입니다.

예를 들어, 각 레이어의 전문가에 MergeModulelist를 사용하는 MoE 모델의 경우, 이론상 최악의 경우 메모리 피크는 모델 크기加上 한 레이어의 전문가 크기가 됩니다.

이러한 최악의 시나리오는 드뭅니다. 실제 메모리 피크는 모델 크기에 가깝게 유지되는 경향이 있습니다.

가역성

시스템은 역 변환을 포함하여 모델을 저장하는 기능을 지원하여 round-trip 저장/로드를 가능하게 합니다. 저장은 두 단계로 수행됩니다. 먼저 역 변환기(reversed converters)가 실행되며(각 텐서는 최대 하나의 변환기에만 매칭됨), 그 다음에는 위의 순서 규칙에 따라 역 이름 변경(reversed renames)이 수행됩니다.

def revert_weight_conversion(model, state_dict):
    """Applies reverse conversions for saving."""
    weight_conversions = getattr(model, "_weight_conversions", None)

    # Reverse all transforms
    reverse_weight_conversion = [
        conversion.reverse_transform() for conversion in weight_conversions
    ]

    # Apply in reverse
    for first_param_name, reversed_converter in conversion_mapping.items():
        realized_value = reversed_converter.convert(first_param_name, model=model)

로드 시점에 사용된 transforms 목록은 모델의 _weight_conversions에 캐시됩니다(실제로 적용된 항목만 유지되므로, PrefixChange와 같은 비생성(non-bijective) 이름 변경은 대칭적으로 올바르게 다시 적용됩니다). 모델이 from_pretrained 없이 인스턴스화되어 _weight_conversions가 없는 경우, revert_weight_conversion는 get_model_conversion_mapping을 통해 매핑을 다시 계산하는 방식으로 폴백하며, 그 결과에서 PrefixChange를 제거합니다(원본 체크포인트에 접두사가 있었는지 알 수 없기 때문입니다).

Target 패턴에는 역방향 처리가 필요한 정규식 요소가 포함될 수 있습니다:

def process_target_pattern(pattern: str) -> tuple[str, str | None]:
    """
    - Removes `^` and `$` anchors
    - Removes negative lookahead/lookbehind
    - Detects capturing groups, replaces with \1
    """

실제 예시

Mixtral 스타일 MoE

체크포인트 형식:

model.layers.0.block_sparse_moe.experts.0.w1.weight  # gate per expert
model.layers.0.block_sparse_moe.experts.0.w2.weight  # down per expert
model.layers.0.block_sparse_moe.experts.0.w3.weight  # up per expert
...
model.layers.0.block_sparse_moe.experts.7.w1.weight

모델 형식:

model.layers.0.mlp.experts.gate_up_proj  # (8, 4096, 28672)
model.layers.0.mlp.experts.down_proj     # (8, 14336, 4096)

변환 매핑(conversion_mapping.py에서):

"mixtral": [
    WeightRenaming(".block_sparse_moe.", ".mlp."),
    WeightConverter(
        source_patterns=[".experts.*.w1.weight", ".experts.*.w3.weight"],
        target_patterns=".experts.gate_up_proj",
        operations=[MergeModulelist(dim=0), Concatenate(dim=1)],
    ),
    WeightConverter(
        source_patterns=[".experts.*.w2.weight"],
        target_patterns=".experts.down_proj",
        operations=[MergeModulelist(dim=0)],
    ),
],

복합 비전-언어 모델(Gemma3)

Gemma3는 base model과 head model에 대해 서로 다른 prefix logic을 사용하는 표준적인 설정을 갖추고 있습니다. 두 항목이 등록되어 있으며(여기서는 공유되는 "llava" / "LlavaModel" 리스트를 가리키는 alias를 통해):

# model_type: applies to Gemma3ForConditionalGeneration / Gemma3ForSequenceClassification
"gemma3": "llava"           # → adds "model." in front of language_model / vision_tower / ...

# class:    applies to the inner Gemma3Model only
"Gemma3Model": "LlavaModel"  # → minimal rename inside the already-prefixed namespace

Gemma3ForConditionalGeneration에 대한 DFS 탐색:

  1. Root — class 조회 실패(Gemma3ForConditionalGeneration가 등록되지 않음), model_type 조회 성공("gemma3") → "llava" 접두어 재작성 변환이 범위 없이 추가되어 language_model, vision_tower, multi_modal_projector가 model.* 아래에 배치됩니다.
  2. 내부 model: Gemma3Model — 클래스 조회가 일치 (Gemma3Model → LlavaModel) → LlavaModel 매핑만 "model" 범위로 적용됩니다. 훨씬 더 넓은 "gemma3" (="llava") 접두어 이름 변경은 여기서 다시 적용되지 않습니다. 이는 의도한 동작입니다. 내부 모델은 접두어가 이미 올바른 네임스페이스에 존재하기 때문입니다.

모든 Llava 계열 VLM(PaliGemma, InternVL, Mistral3, …)에서 동일한 pair shape가 사용됩니다. model_type 항목은 헤드 모델의 prefix surgery를 처리하며, class 항목은 내부 베이스 모델의 매핑을 최소화하도록 유지합니다.

head별 오버라이드를 사용한 더 깊은 중첩(DETR)

DetrForSegmentation은 두 개의 중첩된 레벨 위에 헤드별 오버라이드가 적용된 구조를 보여 줍니다:

DetrForSegmentation                  (class registered: segmentation-only renames)
├── detr: DetrForObjectDetection     (no mapping; just walked through)
│   └── model: DetrModel             (class registered: shared base transforms)
│       └── backbone, encoder, ...
└── mask_head, bbox_attention        (head-specific weights)

두 가지 매핑이 관여됩니다(등록된 키당 하나씩):

"DetrModel":           [WeightRenaming("backbone.conv_encoder", "backbone"), ...]      # shared base
"DetrForSegmentation": [WeightRenaming("mask_head.lay1", "mask_head.conv1.conv"), ...] # head-specific

DFS 탐색:

  1. 루트 DetrForSegmentation는 클래스로 일치하며, segmentation에 추가된 unscoped가 이름이 변경됩니다.
  2. detr: DetrForObjectDetection이 등록되지 않음 → 변환이 추가되지 않으며, DFS가 해당 항목으로 계속 진행됩니다.
  3. detr.model: DetrModel은 클래스로 일치하므로 scope_prefix="detr.model"을 사용하여 기본 변환이 추가됩니다.

DetrForObjectDetection에 별도의 매핑이 필요 없는 이유입니다. 해당 서브트리(DetrModel)에 등록된 유일한 매핑은 자동으로 올바른 경로에 스코프가 적용됩니다.

Class-keyed alias는 추가 등록 없이 기본 매핑을 재사용합니다: "MaskFormerDetrDecoder": "DetrModel"은 MaskFormer 디코더가 자체 클래스 이름으로 동일한 transforms를 적용하도록 합니다.

커스텀 연산(ERNIE 4.5 VL MoE)

내장된 연산이 충분하지 않을 경우, ConversionOps의 하위 클래스를 직접 만들어 사용할 수 있습니다. 예를 들어, ERNIE 4.5 VL MoE는 텍스트와 비전 모달리티 사이에 공유된 expert 목록을 나눠야 하는데, 이는 단일 내장 연산으로 처리할 수 없는 작업입니다. 커스텀 ErnieFuseAndSplitTextVisionExperts 연산은 expert를 두 개의 대상 키에 걸쳐 분리하고 다시 스택합니다:

"ernie4_5_vl_moe": [
    WeightRenaming("vision_model", "vision_tower"),
    WeightConverter(
        source_patterns=["experts.*.down_proj.weight"],
        target_patterns=[
            "text_moe.experts.down_proj",
            "vision_moe.experts.down_proj",
        ],
        operations=[ErnieFuseAndSplitTextVisionExperts(stack_dim=0, concat_dim=1)],
    ),
],

Custom ops는 round-trip 저장/로드를 지원하기 위해 convert()와 reverse_op 속성을 구현해야 합니다.

모델 유형 별칭

많은 모델이 동일한 변환 패턴을 공유합니다:

_MODEL_TO_CONVERSION_PATTERN = {
    "mixtral": "mixtral",
    "minimax": "mixtral",
    "qwen2_moe": "qwen2_moe",
    "deepseek_v2": "qwen2_moe",
    "deepseek_v3": "qwen2_moe",
    "qwen3_moe": "qwen2_moe",
    "olmoe": "qwen2_moe",
    ...
}

동적 로딩 빌딩 블록 재사용

Dynamic weight loading은 전체 모델 체크포인트에 국한되지 않습니다. 체크포인트 키가 매개변수에 어떻게 매핑되는지 설명할 수 있고 대상 모듈이 존재하는지 확인할 수만 있다면, 동일한 구성 요소를 사용하여 어떤 가중치 집합이든 로드할 수 있습니다.

개략적으로, 계약은 다음과 같은 형태를 띱니다:

  1. 모델 네임스페이스를 준비합니다. 로드하려는 모듈/파라미터가 존재하며, 매핑이 대상이 되도록 올바르게 이름이 지정되어 있는지 확인합니다. 어댑터의 경우, 로드 전에 어댑터 모듈이 존재하도록 inject_adapter_in_model(...)을 호출해야 합니다. 커스텀 헤드나 추가 모듈의 경우, 먼저 모델에 인스턴스를 생성합니다.
  2. Describe how to map weights. 변환/이름 변경 목록을 구성합니다(예: _build_peft_weight_mapping(...)) 과 같은 헬퍼에서 WeightConverter 또는 WeightRenaming. 모델 네임스페이스에 맞게 체크포인트 키를 변환, 분할, 병합 또는 이름 변경하는 방법을 정의하는 부분입니다. 주로 다음 세 가지 작업을 수행할 수 있습니다:
    • WeightConverter에 수집된 가중치를 제외한 모든 가중치에 적용될 변환기 목록에 연산을 추가합니다. 일반적으로 이러한 연산은 WeightRenaming 연산이어야 합니다.
    • 각 변환기의 작업 목록에 작업을 추가합니다. Quantization의 경우, 모든 WeightConverter의 작업 목록 뒤에 양자화 작업을 추가하는 방식으로 동작합니다.
    • replace / map 연산을 사용자 정의 연산으로 대체합니다: peft에서 일어나는 일이 바로 이것입니다. 예를 들어 mixtral의 Concatenate 연산을 PeftConcatenate(PEFT에서 정의됨)로 대체합니다. 이렇게 하면 어댑터 체크포인트가 읽힐 때 연결할 가중치가 수집되고, peft에 맞게 올바르게 포맷팅됩니다.
  3. Load + finalize + report. 코어 로더를 사용하여 변환을 수행하고 텐서를 채운 후, 최종 처리를 수행하고 결과를 기록합니다. 구체적으로 이 플로우의 구성은 다음과 같습니다:
    • LoadStateDictConfig(...) + _load_pretrained_model(...)을 사용하여 로드 및 변환합니다.
    • _finalize_load_state_dict(...)을 사용하여 누락되거나 불일치하는 텐서를 meta에서 제거하고, 초기화하며, 가중치를 연결합니다.
    • log_state_dict_report(...)을 사용하여 누락/예상치 못한/불일치하는 키(및 변환 오류)를 보고합니다.

이 API들은 커스텀 코드와 커스텀 가중치 형식을 처리할 수 있도록 제공되며, transformers API의 가장 빠르고 효율적인 가중치 로딩, 샤딩, 그리고 뛰어난 사용 편의성도 함께 활용할 수 있도록 보장합니다!

주요 파일 참조

파일목적
src/transformers/core_model_loading.py핵심 로딩 로직, WeightConverter, WeightRenaming, ConversionOps
src/transformers/conversion_mapping.py내장 매핑 및 서브모델별 구성(get_model_conversion_mapping)
src/transformers/integrations/tensor_parallel.pyTP 샤딩 클래스 및 유틸리티
src/transformers/quantizers/base.py양자화 훅 및 베이스 클래스
Update on GitHub