Transformers documentation

Carga dinámica de pesos

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

Carga dinámica de pesos

Los puntos de control suelen serializarse en un formato que no coincide con lo que el modelo espera en tiempo de ejecución. Los escenarios habituales incluyen:

  1. Pesos fusionados: Los puntos de control almacenan los pesos gate_proj y up_proj por separado, pero el modelo utiliza un gate_up_proj fusionado para mejorar la eficiencia.
  2. Consolidación de expertos MoE: Los pesos de los expertos individuales (experts.0.weight, experts.1.weight, …) deben apilarse en un único tensor 3D.
  3. Nomenclatura antigua: Los puntos de control antiguos utilizan convenciones de nomenclatura diferentes (p. ej., LayerNorm.gamma frente a LayerNorm.weight).
  4. Modelos compuestos: Un modelo de visión-lenguaje contiene dos submódulos PreTrainedModel, cada uno con su propia convención de punto de control.
  5. Cuantización: Los pesos pueden almacenarse en formatos cuantizados que requieren deserialización.

La carga dinámica de pesos aborda este problema aplicando operaciones programadas y reversibles a los tensores del punto de control a medida que se cargan. Transformers expone esta funcionalidad a través de WeightConverter y WeightRenaming, que describen cómo una o varias claves de punto de control se mapean a uno o varios parámetros del modelo y qué ConversionOps componibles deben ejecutarse en los tensores coincidentes. Este enfoque se adapta a nuevos diseños de pesos, admite mezclas de expertos (MoE) cuantizadas e integra la paralelización de tensores.

Esta guía muestra cómo usar WeightConverter para convertir tensores. Los mapeos de conversión se encuentran en conversion_mapping.py; un mapeo registrado se indexa mediante una cadena model_type (p. ej. "mixtral") o un nombre de clase (p. ej. "LlavaModel").

Pipeline completo de carga

Todos los modelos pasan por el sistema de carga dinámica de pesos. El mapeo de conversión es un paso opcional dentro de ese sistema que solo se activa cuando el modelo tiene entradas registradas para su nombre de clase o 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                                │
                         └───────────────────────────────────────────────────────────┘
PasoCuando se activa
Carga dinámicaSiempre, para todos los modelos
Mapeo de conversiónSolo cuando la clase del modelo o model_type está registrada en _MODEL_TO_CONVERSION_PATTERN
Fragmentación de TPSolo cuando se usa DistributedConfig(tp_size=N) y el modelo tiene base_model_tp_plan
Desquantización/deserializaciónSolo al cargar un punto de control pre-cuantizado
CuantizaciónSolo cuando se proporciona una configuración de cuantización y los pesos no están precuantizados

Modelos densos (p. ej., Llama)

Para la mayoría de los modelos densos, el formato del punto de control coincide directamente con el formato del modelo, por lo que no se necesita un mapeo de conversión. Algunos modelos aún pueden requerir un cambio de nombre (p. ej., convenciones de nomenclatura heredadas). La partición de TP sigue aplicándose cuando está habilitada.

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

Los puntos de control heredados pueden usar convenciones de nomenclatura anteriores que se gestionan mediante renombrados integrados aplicados a todos los modelos:

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

Modelos MoE (p. ej., Mixtral)

Para los modelos MoE, el formato del punto de control difiere del formato del modelo. El mapeo de conversión transforma los pesos de expertos separados en tensores 3D fusionados, y la partición de TP se aplica después de la conversión.

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  ─┘

Modelos compuestos (p. ej., visión-lenguaje)

Un PreTrainedModel puede contener otros submódulos PreTrainedModel. Cada submodelo puede tener su propio mapa de conversión, registrado en función de su nombre de clase o de su model_type. Cuando se carga el modelo principal, get_model_conversion_mapping recorre los submodelos en orden de profundidad, recopila sus mapas y asigna automáticamente el ámbito de cada transformación a la ruta de su submódulo mediante 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 es la ruta con puntos del submódulo ("vision_model", "language_model.model", etc.). Una transformación con ámbito solo se activa en las claves que comienzan con f"{scope_prefix}."; el prefijo se elimina antes de la coincidencia de patrones y se vuelve a adjuntar después de la sustitución, de modo que el mapeo de cada submodelo se escribe relativo al submodelo, exactamente como si fuera la raíz.

Arquitectura

El sistema se construye en torno a varios componentes clave definidos en core_model_loading.py:

Fase 1 — Procesamiento por clave (recorre las claves del checkpoint):

  1. Recorra la lista de transformaciones una sola vez. Cada WeightRenaming que coincida se ejecutará (p. ej., block_sparse_moe → mlp), y a lo sumo un WeightConverter puede reclamar la clave (p. ej., experts.*.w1.weight).
  2. Fragmentar (TP) y enviar al dispositivo de forma asíncrona mediante ThreadPoolExecutor.
  3. Reunir los tensores con el mismo source_pattern (p. ej., todos los pesos de expertos MoE, proyecciones gate + up).

Fase 2 — Procesamiento por mapeo (recorre los mapeos recopilados):

  1. Desquantizar/deserializar (solo puntos de control pre-quantizados).
  2. Aplicar la cadena de ConversionOps: Chunk, Concatenate, MergeModulelist, Transpose, etc.
  3. Cuantizar sobre la marcha (si no está precuantizado).
  4. Establecer el parámetro Set parameter en el modelo.

WeightTransform

La clase base que gestiona la coincidencia de patrones y la recopilación de tensores:

  • Compilación de patrones: Los patrones de origen son expresiones regulares completas que se comparan con re.search(). El comodín * coincide con cualquier componente indexable y agrupa todas las coincidencias para operaciones por lotes.
  • Grupos de captura y referencias inversas: Los grupos de captura en los patrones de origen se pueden referenciar como \1, \2, … en los patrones de destino para conservar subcadenas (p. ej., índices de capas) durante el cambio de nombre.
  • Alcance: scope_prefix (establecido automáticamente por submodelo mediante get_model_conversion_mapping) restringe la transformación a las claves bajo esa ruta. El prefijo se elimina antes de la coincidencia y se vuelve a adjuntar después de la sustitución.
  • Renombrado de claves: rename_source_key() aplica la expresión regular (con el manejo del ámbito) y devuelve (renamed_key, source_pattern) para que el cargador sepa qué convertidor, si lo hay, reclamó la clave.
  • Recolección de tensores: add_tensor() acumula los tensores resueltos (o Future) bajo su source_pattern para que todos los tensores necesarios para una única conversión (p. ej., todos los pesos de expertos MoE) se agrupen antes de que se ejecute la cadena de operaciones.
  • Reversibilidad: reverse_transform() intercambia los patrones de origen ↔ destino e invierte cada operación, de modo que la misma lista invertida dirige el guardado.
  • Seguimiento: was_used() informa si la transformación coincidió realmente con alguna clave durante la carga; esto es necesario para que los renombrados no biyectivos (p. ej. PrefixChange que añade un prefijo que ya puede estar presente) puedan reaplicarse de forma simétrica al guardar.

WeightRenaming

WeightRenaming es un WeightTransform especializado para renombrar claves sin realizar operaciones sobre los tensores. A diferencia de WeightConverter, un WeightRenaming no reclama la clave, por lo que varios renombrados pueden encadenarse libremente. En la ruta de carga, todos los renombrados se ejecutan antes del conversor; en la ruta de guardado (lista invertida), todos los renombrados invertidos se ejecutan después del conversor invertido; consulte la regla de ordenamiento anterior.

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

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

PrefixChange es un envoltorio de nivel superior de WeightRenaming para el caso común de eliminar o añadir un componente completo de la ruta. El parámetro opcional model_prefix limita la operación a las claves bajo ese espacio de nombres:

# "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 extiende WeightTransform con una cadena de ConversionOps que actúan sobre los tensores recopilados. Las cuatro cardinalidades admitidas son:

CardinalidadPatrones de origenPatrones objetivoOperación típica
uno a uno11Transpose, PermuteForRope
uno a muchos1>1Chunk (p. ej., desempaquetar qkv_proj)
muchos a uno>11Concatenar, MergeModulelist (p. ej., fusionar expertos)
muchos a muchos>1>1solo con operaciones que lo admitan explícitamente (p. ej. ErnieFuseAndSplitTextVisionExperts)
WeightConverter(
    source_patterns=[".experts.*.w1.weight", ".experts.*.w3.weight"],
    target_patterns=".experts.gate_up_proj",
    operations=[MergeModulelist(dim=0), Concatenate(dim=1)],
)

Un WeightConverter también es reversible: reverse_transform() intercambia origen ↔ destino y reemplaza cada operación con su reverse_op, de modo que un mapeo de conversión registrado es bidireccional por construcción (la carga utiliza la lista tal cual, el guardado la utiliza invertida).

Ordenación de renombrados y conversores

Coloca las entradas de WeightRenaming antes que las de WeightConverter y mantén sus hojas disjuntas: los renombramientos normalizan las claves que consumen los conversores, pero nunca deben apuntar a una hoja que un conversor produzca. La ruta de guardado depende de esto para invertir el mapeo en dos fases (primero inverso de conversores, luego inverso de renombramientos).

La lista de transformaciones se recorre en orden, una vez por cada clave de punto de control. Para cada clave:

  • Cada WeightRenaming que coincide se ejecuta; se pueden encadenar varios renombramientos.
  • El primer WeightConverter que coincide con claims la clave. Los conversores posteriores se omiten; esto garantiza que el tensor se enrute a un único conversor para el paso de fusión/división, y es la razón por la que dos conversores con una intención superpuesta constituirían una configuración incorrecta.
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"

Operaciones de conversión

La clase WeightConverter tiene varias operaciones que se ejecutan cuando se llama a from_pretrained() para transformar los tensores de origen del punto de control en los tensores de destino del modelo.

Las operaciones son totalmente reversibles. Al guardar, se revierten las conversiones y se devuelve el punto de control original, lo que permite trabajar fácilmente entre diferentes frameworks.

OperaciónInvertir
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

La operación Chunk divide un tensor en partes iguales a lo largo de una dimensión. Por ejemplo, si un modelo espera Q, K y V como tres tensores separados en lugar de un único tensor.

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

Concatenar

La operación Concatenate fusiona tensores separados en un único tensor. Por ejemplo, cuando un modelo espera Q, K y V como un solo tensor en lugar de tensores separados.

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

MergeModulelist

MergeModulelist fusiona una lista de tensores 2D en un único tensor 3D. Por ejemplo, puedes combinar MergeModulelist con Concatenate para apilar los expertos de un MoE y empaquetarlos en un solo tensor.

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 divide un tensor 3D en una lista de tensores 2D. Por ejemplo, puede dividir una pila de expertos en expertos individuales.

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

PermuteForRope

PermuteForRope convierte los pesos del formato entrelazado al formato sin/cos. Por ejemplo, puedes componer Chunk con PermuteForRope para dividir un tensor QKV fusionado y aplicar la permutación RoPE sin/cos a Q y K.

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()],
)

Transponer

Transpose intercambia las dimensiones de un tensor. Es útil para convertir los formatos de pesos entre diferentes convenciones.

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

Encadenamiento de operaciones

Las operaciones se pueden encadenar para realizar transformaciones complejas. Las operaciones se ejecutan en orden, y la salida de cada operación se convierte en la entrada de la siguiente.

Ejemplo: conversión de 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)
    ],
)

Flujo de datos:

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

Reglas de coincidencia de patrones

  • Los patrones de origen son expresiones regulares completas evaluadas con re.search(). ^ ancla al inicio de la clave scoped (después de eliminar scope_prefix).
  • * es un comodín por índice que recopila todos los tensores coincidentes bajo el mismo patrón de origen (conservando el orden del checkpoint para una concatenación correcta).
  • Los grupos de captura en los patrones de origen se pueden referenciar como \1, \2, … en los patrones de destino para conservar partes de la clave original (p. ej., los índices de capa).
  • Las transformaciones con ámbito (scope_prefix establecido) solo coinciden con claves que comienzan con f"{scope_prefix}.".

Registro de un mapeo de conversión

Se registra una lista de conversiones contra una clave de cadena: ya sea un model_type (p. ej. "mixtral") o un nombre de clase (p. ej. "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)],
        ),
    ],
)

Reglas de búsqueda

Cuando get_model_conversion_mapping procesa un PreTrainedModel, cada sub-PreTrainedModel se visita en orden DFS (nn.Module.named_modules() filtrado a instancias de PreTrainedModel). Cada submodelo se resuelve en dos etapas: primero se ejecuta un filtro de código personalizado y luego se busca el mapeo registrado.

Los modelos de código personalizado se omiten antes de que se realice cualquier búsqueda. Un submodelo se considera de código personalizado cuando is_custom_code() devuelve True, lo que abarca los modelos cargados desde el Hub con trust_remote_code=True y cualquier clase de modelo que no esté en Transformers. Los modelos de código personalizado no heredan un mapeo de conversión, ya que su nombre de clase o model_type podría coincidir con el de un modelo nativo. Por ejemplo, una arquitectura personalizada que establece model_type="mixtral" no aplicará las transformaciones de fusión de expertos integradas de Mixtral.

Registre un modelo de código personalizado explícitamente con register_checkpoint_conversion_mapping para aplicar un mapeo de conversión. El registro añade el nombre de la clase o model_type al conjunto de mapeos registrados por el usuario, lo que exime al modelo del salto de código personalizado y lo enruta a través de la búsqueda normal descrita a continuación.

Para cada submodelo que supera el filtro de código personalizado:

  1. Se intenta primero la búsqueda por nombre de clase y, a continuación, model_type. Si ambos están registrados para el mismo módulo, la correspondencia por nombre de clase tiene prioridad y la de model_type se ignora para ese módulo; esto permite que una cabecera de tarea (p. ej. LlavaForConditionalGeneration) anule la línea base compartida de model_type ("llava").
  2. El mapeo seleccionado tiene scope_prefix establecido en la ruta con puntos del submódulo ("" para la raíz).
  3. Ancestor-based deduplication determina si se conserva el mapeo:
    • Si una ruta ancestral ya ha reclamado el mismo identificador (nombre de clase o model_type), el submódulo se omite: el mapeo no acotado o de mayor alcance del ancestro ya cubre este subárbol.
    • Si solo un hermano lo ha reclamado, el submódulo se conserva con su propio scope_prefix. Cada hermano obtiene su propio mapeo con ámbito.

Las listas de elementos vistos para el nombre de clase y model_type se rastrean por separado, con un matiz: cuando un módulo coincide mediante su nombre de clase, su model_type no se añade a la lista de vistos. Esto permite que otros módulos que comparten el mismo model_type pero no tienen un mapeo específico de clase (p. ej. DetrModel bajo DetrForSegmentation) sigan siendo accesibles a través de la búsqueda por model_type.

Mapeos basados en clases frente a alias de model_type

Ambos estilos coexisten en _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",
    ...
}

Se prefieren las claves de nombre de clase cuando el model_type es compartido pero una clase específica necesita un comportamiento diferente.

Integración de paralelismo de tensores

El sistema de carga dinámica se integra con la paralelización de tensores (TP) a través de la jerarquía TensorParallelLayer definida en src/transformers/integrations/tensor_parallel.py.

Cuando TP está habilitado, los tensores se fragmentan durante la materialización, no después. Esto significa que cada rank solo carga la porción del tensor que necesita.

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)

Estilos de paralelismo disponibles

EstiloDimensión de fragmento de pesosDescripción
colwise-2Por columnas: características de salida fragmentadas
rowwise-1Por filas: características de entrada fragmentadas
packed_colwise-2Para pesos fusionados (gate_up_proj)
packed_rowwise-1Para pesos fusionados
embedding_rowwise0Paralelismo de vocabulario
grouped_gemm0Paralelismo de expertos para MoE
sequence_parallelNingunoSin particionamiento de pesos

Gestión de pesos empaquetados

Para pesos fusionados como gate_up_proj, se requiere especial cuidado para particionar correctamente:

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]
    """

La operación de TP se almacena en WeightTransform y se aplica después de las operaciones de conversión:

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()
    )

Integración de cuantización

La cuantización se integra en el proceso de carga de dos maneras, dependiendo de si el punto de control ya está cuantizado:

  • Puntos de control pre-cuantizados: el cuantizador proporciona instancias de WeightConverter (mediante get_weight_conversions()) que deserializan tensores cuantizados. Los dtypes del punto de control se conservan para evitar conversiones no deseadas.
  • Cuantización en tiempo de ejecución: El cuantizador proporciona una operación de cuantización que se aplica después de las operaciones de conversión, cuantizando los pesos a medida que se cargan.

El cuantizador también puede reescribir la lista completa de conversiones al final de get_model_conversion_mapping mediante update_weight_conversions(...); por ejemplo, el decuantizador FP8 antepone una operación Fp8Dequantize a cada convertidor existente para que las escalas por bloque se apliquen antes de que las operaciones de fusión de expertos / concatenación aplanen la estructura por experto.

Carga de modelos rápida y eficiente

Cargar un modelo es más rápido y consume menos memoria porque el cargador sabe qué tensores son necesarios para las operaciones y programa su materialización de forma perezosa.

El loader analiza el checkpoint una vez para descubrir coincidencias de patrones y recopilar tensores. Los almacena como objetos Future y los envía a un pool de hilos para una carga asíncrona sin bloquear el GIL. Un parámetro comienza a cargarse en cuanto un hilo queda disponible para él.

Si su sistema ejecuta otros procesos intensivos, varios hilos pueden ralentizar la carga en lugar de acelerarla. En este caso, establezca la variable de entorno HF_DEACTIVATE_ASYNC_LOAD=1 para cargar los pesos de forma secuencial.

El valor predeterminado es 4 hilos para la carga asíncrona de parámetros. Esto ofrece el mejor equilibrio entre los distintos escenarios de carga y el hardware. El trabajo está limitado principalmente por la E/S, pero dependiendo del hardware del acelerador y del dtype requerido en la carga, puede convertirse en un proceso limitado por la CPU/GPU si el dtype difiere del serializado (esto requiere una operación de copia adicional).

Carga asíncrona frente a carga síncrona

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)

La carga síncrona se utiliza cuando:

  • Se establece la variable de entorno HF_DEACTIVATE_ASYNC_LOAD=1.
  • El descargado en disco está habilitado (las restricciones de memoria requieren una carga secuencial).
  • La cuantización en tiempo de ejecución está habilitada (evita que los hilos de trabajo se adelanten al paso de cuantización).

Flujo de materialización

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)

Eficiencia de memoria

Al convertir un peso, el conversor espera a que todos los tensores necesarios se materialicen si aún no se han cargado. Por ejemplo, la operación MergeModulelist requiere que todos los pesos de ModuleList estén cargados antes de fusionarlos.

La concatenación de tensores requiere una copia temporal, por lo que operaciones como MergeModulelist y Concatenate necesitan el doble de memoria que los tensores subyacentes durante la conversión. Una vez fusionados, solo el tensor resultante permanece en memoria. El pico de memoria teórico en el peor caso es el tamaño del modelo más los tensores necesarios para la operación de MergeModulelist o Concatenate más grande.

Este caso más desfavorable solo se produce cuando todos los demás parámetros se han cargado antes de que se ejecute la conversión exigente. Dos escenarios lo provocan.

  1. Todos los parámetros se cargaron de forma asíncrona antes de entrar en la conversión exigente (el pool de hilos fue más rápido que la cola de conversión).
  2. La conversión más exigente es la última.

Por ejemplo, en un modelo MoE que utiliza MergeModulelist para los expertos de cada capa, el pico de memoria teórico en el peor caso es el tamaño del modelo más los expertos de una capa.

Estos escenarios peores son poco frecuentes. El pico de memoria real tiende a mantenerse cerca del tamaño del modelo.

Reversibilidad

El sistema permite guardar modelos con las transformaciones inversas, lo que habilita un ciclo completo de guardado y carga. El guardado se realiza en dos fases: primero los conversores invertidos (cada tensor coincide con como mucho uno) y, a continuación, los renombrados invertidos, según la regla de ordenación anterior.

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)

La lista de transforms utilizados en el momento de la carga se almacena en caché en el modelo como _weight_conversions (solo se conservan las entradas que se activaron realmente, por lo que los renombrados no biyectivos, como PrefixChange, se vuelven a aplicar de forma simétrica y correcta). Cuando el modelo se instanció sin from_pretrained (y, por lo tanto, no tiene _weight_conversions), revert_weight_conversion recurre a recalcular el mapeo mediante get_model_conversion_mapping y elimina cualquier PrefixChange de dicho mapeo (no podemos saber si el checkpoint original tenía el prefijo).

Los patrones de destino pueden contener elementos de expresiones regulares que requieren procesamiento para la dirección inversa:

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

Ejemplos reales

MoE de estilo Mixtral

Formato de punto de control:

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

Formato del modelo:

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

Mapa de conversión (de 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)],
    ),
],

Modelo compuesto de visión y lenguaje (Gemma3)

Gemma3 tiene la configuración canónica de “lógica de prefijo diferente para el modelo de cabecera que para el modelo base”. Se registran dos entradas (aquí mediante alias que apuntan a las listas compartidas de "llava" / "LlavaModel"):

# 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

Recorrido DFS para Gemma3ForConditionalGeneration:

  1. Root — la búsqueda de la clase falla (Gemma3ForConditionalGeneration no está registrada), la búsqueda de model_type acierta ("gemma3") → se añaden las transformaciones de reescritura con el prefijo "llava" sin ámbito, colocando language_model, vision_tower y multi_modal_projector bajo model.*.
  2. Modelo interno model: Gemma3Model — las coincidencias de búsqueda de clase (Gemma3Model → LlavaModel) → solo se aplica el mapeo de LlavaModel, limitado a "model". Los renombramientos de prefijo mucho más amplios de "gemma3" (="llava") no se vuelven a aplicar aquí, que es exactamente lo que se desea: el modelo interno reside en un espacio de nombres donde el prefijo ya es correcto.

El mismo par de formas se utiliza en todos los VLM de la familia Llava (PaliGemma, InternVL, Mistral3, …). La entrada de model_type gestiona la cirugía de prefijo del modelo de cabecera; la entrada de clase mantiene al mínimo el mapeo del modelo base interno.

Anidamiento más profundo con una sobrescritura específica de la cabeza (DETR)

DetrForSegmentation muestra una sobrescritura específica de la cabeza superpuesta a dos niveles anidados:

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)

Están implicados dos mapeos (uno por cada clave registrada):

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

Recorrido DFS:

  1. La raíz DetrForSegmentation coincide por clase → se renombran las adiciones unscoped a segmentación.
  2. detr: DetrForObjectDetection no está registrado → no se agregan transformaciones; DFS continúa hacia su interior.
  3. detr.model: DetrModel coincide por clase → se añaden las transformaciones base con scope_prefix="detr.model".

Por eso DetrForObjectDetection no necesita su propio mapeo: el único mapeo registrado en su subárbol (DetrModel) se aplica automáticamente a la ruta correcta.

Los alias con clave de clase reutilizan el mapeo base sin un registro adicional: "MaskFormerDetrDecoder": "DetrModel" hace que un decodificador de MaskFormer adopte las mismas transformaciones bajo su propio nombre de clase.

Operaciones personalizadas (ERNIE 4.5 VL MoE)

Cuando las operaciones integradas no son suficientes, puede crear una subclase personalizada de ConversionOps. Por ejemplo, ERNIE 4.5 VL MoE necesita dividir una lista de expertos compartidos entre las modalidades de texto y visión, algo que ninguna operación integrada individual gestiona. La operación personalizada ErnieFuseAndSplitTextVisionExperts divide y vuelve a apilar expertos entre dos claves de destino:

"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)],
    ),
],

Las operaciones personalizadas deben implementar convert() y la propiedad reverse_op para admitir el guardado y la carga de ida y vuelta.

Alias de tipos de modelo

Muchos modelos comparten patrones de conversión:

_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",
    ...
}

Reutilizar los bloques de construcción de la carga dinámica

La carga dinámica de pesos no se limita a los puntos de control completos del modelo. Los mismos componentes básicos permiten cargar cualquier conjunto de pesos, siempre que puedas describir cómo se mapean las claves del punto de control a los parámetros y asegures que los módulos de destino existan.

A alto nivel, el contrato se ve así:

  1. Prepara el espacio de nombres del modelo. Asegúrate de que los módulos/parámetros que deseas cargar estén presentes y tengan los nombres que tu mapeo utilizará como destino. En el caso de adaptadores, esto implica llamar a inject_adapter_in_model(...) para que los módulos del adaptador existan antes de la carga. Para cabezales personalizados o módulos adicionales, instáncialos primero en el modelo.
  2. Describe how to map weights. Crea una lista de conversión/renombrado (por ejemplo, en un helper como _build_peft_weight_mapping(...)) utilizando WeightConverter o WeightRenaming. Aquí es donde se expresa cómo deben convertirse, dividirse, fusionarse o renombrar las claves del checkpoint para que coincidan con el espacio de nombres de tu modelo. Básicamente, puedes hacer tres cosas:
    • añada operaciones a la lista de conversores: estas se aplicarán a todos los pesos, excepto a los recopilados en cualquiera de los WeightConverter. En general, estas deberían ser operaciones de WeightRenaming
    • añadir operaciones a la lista de operaciones de cada conversor: esto es lo que ocurre con Quantization, donde simplemente se añade una operación de cuantización después de la lista de operaciones de cualquier WeightConverter.
    • sustituir / mapear operaciones por operaciones personalizadas: esto es lo que ocurre con peft. Sustituimos la operación Concatenate de, por ejemplo, mixtral, por PeftConcatenate (que está definida en PEFT). De esta manera, cuando se lee el checkpoint del adaptador, se recogen los pesos que deben concatenarse y se formatean correctamente para peft
  3. Load + finalize + report. Utilice el cargador principal para realizar la conversión y poblar los tensores, y luego finalice y registre los resultados. Concretamente, este flujo es:
    • LoadStateDictConfig(...) + _load_pretrained_model(...) para cargar y convertir.
    • _finalize_load_state_dict(...) para mover los tensores faltantes o con discrepancias fuera de meta, inicializarlos y vincular los pesos.
    • log_state_dict_report(...) para informar sobre claves faltantes/no esperadas/no coincidentes (y errores de conversión).

Estas APIs se exponen para permitirte manejar código personalizado y formatos de pesos personalizados, pero también para asegurarte de que aproveches la carga de pesos más rápida y eficiente, el particionado y la buena calidad de vida de la API de transformers!

Referencia de archivos clave

ArchivoPropósito
src/transformers/core_model_loading.pyLógica principal de carga, WeightConverter, WeightRenaming, ConversionOps
src/transformers/conversion_mapping.pyMapeos integrados y composición por submodelo (get_model_conversion_mapping)
src/transformers/integrations/tensor_parallel.pyClases y utilidades de particionamiento de TP
src/transformers/quantizers/base.pyGanchos de cuantización y clase base
Update on GitHub