Transformers documentation
Carga dinámica de pesos
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:
- Pesos fusionados: Los puntos de control almacenan los pesos
gate_projyup_projpor separado, pero el modelo utiliza ungate_up_projfusionado para mejorar la eficiencia. - Consolidación de expertos MoE: Los pesos de los expertos individuales (
experts.0.weight,experts.1.weight, …) deben apilarse en un único tensor 3D. - Nomenclatura antigua: Los puntos de control antiguos utilizan convenciones de nomenclatura diferentes (p. ej.,
LayerNorm.gammafrente aLayerNorm.weight). - Modelos compuestos: Un modelo de visión-lenguaje contiene dos submódulos
PreTrainedModel, cada uno con su propia convención de punto de control. - 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 │
└───────────────────────────────────────────────────────────┘| Paso | Cuando se activa |
|---|---|
| Carga dinámica | Siempre, para todos los modelos |
| Mapeo de conversión | Solo cuando la clase del modelo o model_type está registrada en _MODEL_TO_CONVERSION_PATTERN |
| Fragmentación de TP | Solo cuando se usa DistributedConfig(tp_size=N) y el modelo tiene base_model_tp_plan |
| Desquantización/deserialización | Solo al cargar un punto de control pre-cuantizado |
| Cuantización | Solo 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.weightLos 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.biasModelos 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):
- Recorra la lista de transformaciones una sola vez. Cada
WeightRenamingque coincida se ejecutará (p. ej.,block_sparse_moe→mlp), y a lo sumo un WeightConverter puede reclamar la clave (p. ej.,experts.*.w1.weight). - Fragmentar (TP) y enviar al dispositivo de forma asíncrona mediante
ThreadPoolExecutor. - 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):
- Desquantizar/deserializar (solo puntos de control pre-quantizados).
- Aplicar la cadena de ConversionOps:
Chunk,Concatenate,MergeModulelist,Transpose, etc. - Cuantizar sobre la marcha (si no está precuantizado).
- 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 medianteget_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 (oFuture) bajo susource_patternpara 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.PrefixChangeque 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:
| Cardinalidad | Patrones de origen | Patrones objetivo | Operación típica |
|---|---|---|---|
| uno a uno | 1 | 1 | Transpose, PermuteForRope |
| uno a muchos | 1 | >1 | Chunk (p. ej., desempaquetar qkv_proj) |
| muchos a uno | >1 | 1 | Concatenar, MergeModulelist (p. ej., fusionar expertos) |
| muchos a muchos | >1 | >1 | solo 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
WeightRenamingque coincide se ejecuta; se pueden encadenar varios renombramientos. - El primer
WeightConverterque 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ón | Invertir |
|---|---|
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_upReglas 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 eliminarscope_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_prefixestablecido) solo coinciden con claves que comienzan conf"{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_mappingpara aplicar un mapeo de conversión. El registro añade el nombre de la clase omodel_typeal 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:
- 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 demodel_typese ignora para ese módulo; esto permite que una cabecera de tarea (p. ej.LlavaForConditionalGeneration) anule la línea base compartida demodel_type("llava"). - El mapeo seleccionado tiene
scope_prefixestablecido en la ruta con puntos del submódulo (""para la raíz). - 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.
- Si una ruta ancestral ya ha reclamado el mismo identificador (nombre de clase o
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
| Estilo | Dimensión de fragmento de pesos | Descripción |
|---|---|---|
colwise | -2 | Por columnas: características de salida fragmentadas |
rowwise | -1 | Por filas: características de entrada fragmentadas |
packed_colwise | -2 | Para pesos fusionados (gate_up_proj) |
packed_rowwise | -1 | Para pesos fusionados |
embedding_rowwise | 0 | Paralelismo de vocabulario |
grouped_gemm | 0 | Paralelismo de expertos para MoE |
sequence_parallel | Ninguno | Sin 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
dtyperequerido en la carga, puede convertirse en un proceso limitado por la CPU/GPU si eldtypedifiere 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.
- 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).
- 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.weightFormato 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 namespaceRecorrido DFS para Gemma3ForConditionalGeneration:
- Root — la búsqueda de la clase falla (
Gemma3ForConditionalGenerationno está registrada), la búsqueda de model_type acierta ("gemma3") → se añaden las transformaciones de reescritura con el prefijo"llava"sin ámbito, colocandolanguage_model,vision_towerymulti_modal_projectorbajomodel.*. - Modelo interno
model: Gemma3Model— las coincidencias de búsqueda de clase (Gemma3Model→LlavaModel) → solo se aplica el mapeo deLlavaModel, 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-specificRecorrido DFS:
- La raíz
DetrForSegmentationcoincide por clase → se renombran las adiciones unscoped a segmentación. detr: DetrForObjectDetectionno está registrado → no se agregan transformaciones; DFS continúa hacia su interior.detr.model: DetrModelcoincide por clase → se añaden las transformaciones base conscope_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í:
- 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. - Describe how to map weights. Crea una lista de conversión/renombrado (por ejemplo, en un helper como
_build_peft_weight_mapping(...)) utilizando WeightConverter oWeightRenaming. 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 deWeightRenaming - 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 cualquierWeightConverter. - sustituir / mapear operaciones por operaciones personalizadas: esto es lo que ocurre con
peft. Sustituimos la operaciónConcatenatede, por ejemplo,mixtral, porPeftConcatenate(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 parapeft
- añada operaciones a la lista de conversores: estas se aplicarán a todos los pesos, excepto a los recopilados en cualquiera de los
- 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 demeta, 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
| Archivo | Propósito |
|---|---|
src/transformers/core_model_loading.py | Lógica principal de carga, WeightConverter, WeightRenaming, ConversionOps |
src/transformers/conversion_mapping.py | Mapeos integrados y composición por submodelo (get_model_conversion_mapping) |
src/transformers/integrations/tensor_parallel.py | Clases y utilidades de particionamiento de TP |
src/transformers/quantizers/base.py | Ganchos de cuantización y clase base |