PEFT documentation
ShadowPEFT
ShadowPEFT
ShadowPEFT amplía un modelo base decodificador congelado con una red shadow ligera y preentrenable que se ejecuta en paralelo con el backbone. Un pequeño backbone shadow produce un estado shadow inicial s^(0), que luego recorre el bucle decodificador del modelo base: en cada bloque objetivo, la discrepancia entre los estados ocultos base y el estado shadow se inyecta de nuevo en la entrada del bloque (una corrección de bajo rango) y el estado shadow se avanza mediante una actualización residual con compuerta calculada a partir de la salida del bloque. Solo se entrenan los componentes shadow; el modelo base permanece congelado.
Input
├──► Shadow backbone (small, trainable) ──► s^(0)
└──► Base model (frozen, large)
block_0 ◄── inject(h, s) ─► h_0 ──► update ─► s_1
block_1 ◄── inject(h, s) ─► h_1 ──► update ─► s_2
... (the (hidden, shadow) pair rides the loop together)Dado que la adaptación es una trayectoria dependiente de la entrada en el espacio de capas (el estado sombra evoluciona con los datos) y no una delta estática en el espacio de pesos, ShadowPEFT no puede fusionarse con los pesos base. Llamar a merge, merge_adapter o merge_and_unload genera un error explícito. Para los modelos de lenguaje de Transformers, puedes obtener la red sombra ligera de forma independiente con model.base_model.unload_shadow(), que devuelve un DetachedShadowModel autónomo. La descarga independiente no está soportada para los modelos de Diffusers porque la reconstrucción de un desruidor completo depende de la arquitectura.
Agregar varios adaptadores, alternar entre ellos con set_adapter, eliminarlos y habilitarlos/deshabilitarlos funciona igual que con otros métodos PEFT. Solo puede estar activo un adaptador a la vez, ya que el estado sombra es una única trayectoria a través de la red.
El backbone de sombra se puede construir de dos maneras, controladas por ShadowConfig.shadow_model:
"mirror"(predeterminado): se crea automáticamente un backbone de sombra más pequeño. Los modelos de lenguaje utilizan una copia reducida de la arquitectura base. Las arquitecturas de Diffusers con un backend registrado utilizan un modelo de arquitectura similar reducido, inicializado a partir de pesos base seleccionados; las arquitecturas compatibles sin backend recurren a un MLP residual por token. Cuando el tamaño oculto de la sombra difiere del base, una proyección entrenada cubre la diferencia.- un id de modelo o ruta local: el backbone se carga como un modelo preentrenado más pequeño. Los modelos de Transformers usan
AutoModel; los backends de Diffusers registrados definen su propia carga de checkpoints compatible.
El soporte de Diffusers con conciencia de la arquitectura se selecciona automáticamente a partir de la clase del modelo. Flux2 actualmente tiene un backend registrado; otros modelos de Diffusers compatibles basados en transformadores utilizan el respaldo genérico de MLP. La función independiente unload_shadow() sigue sin estar soportada para todos los modelos de Diffusers, ya que la reconstrucción de un denoiser completo depende de la arquitectura.
En comparación con los métodos de estilo LoRA, ShadowPEFT añade más parámetros y cómputo (ejecuta una red paralela y envuelve bloques completos del decodificador), pero el adaptador es una red autónoma que se puede entrenar de forma centralizada, reutilizar entre tareas e inicializar a partir de un modelo pequeño preentrenado. Una pérdida auxiliar opcional (auxiliary_loss_weight) aplica la cabeza de la tarea al estado inicial de sombra s^(0) y la suma a la pérdida de la tarea, animando a la ruta de sombra desmontable a resolver la tarea por sí sola. Para LM causal, se reutiliza la cabeza de salida base; incluye "lm_head" en modules_to_save para entrenarla y guardarla a través del mecanismo estándar de PEFT.
Caché KV
ShadowPEFT admite decodificación incremental con una caché KV dual: una para el modelo base congelado y otra para el esqueleto de sombra. La inyección/actualización es local al token, por lo que un token nuevo solo necesita su propio estado de sombra s; la causalidad mantiene válidas las claves/valores del prefijo base (calculadas bajo inyección). El objeto emparejado es una ShadowCache, devuelta como past_key_values cuando use_cache=True. Puedes pasar use_cache=True a generate() como de costumbre.
out = model.generate(input_ids, max_new_tokens=32) # dual KV cache enabled by default
# or explicitly:
out = model.generate(input_ids, use_cache=True, max_new_tokens=32)use_cache=False sigue funcionando y reprocesa la secuencia completa en cada paso (útil para depurar).
Uso
from transformers import AutoModelForCausalLM
from peft import ShadowConfig, get_peft_model
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
config = ShadowConfig(r=8, shadow_num_hidden_layers=1, task_type="CAUSAL_LM")
model = get_peft_model(model, config)
model.print_healthcheck()
out = model.generate(input_ids, max_new_tokens=32)Para inicializar el shadow backbone a partir de un modelo preentrenado más pequeño, pase su id o ruta como shadow_model:
config = ShadowConfig(shadow_model="Qwen/Qwen3-0.6B", task_type="CAUSAL_LM")
model = get_peft_model(base_model, config)Evaluando la ruta de sombra
Por defecto, la salida del modelo (logits) es el modelo base adaptado por sombra: las correcciones de sombra se inyectan en los estados ocultos del modelo base en cada capa, por lo que logits ya refleja ShadowPEFT (use model.disable_adapter() para obtener el modelo base simple para comparar). La pérdida auxiliar entrena adicionalmente la ruta de sombra para resolver la tarea por sí sola.
Para evaluar la red de sombra independiente de un modelo de lenguaje de Transformers (el modelo desmontable y ligero, análogo a ShadowPEFT de merge_and_unload), use unload_shadow(). Devuelve head(projection(backbone(x))) como un modelo de tarea normal que puede evaluar como cualquier otro: para una tarea de LM causal es un LM causal con capacidad de generación (admite generate() y caché de KV), y para una tarea de clasificación de secuencias agrupa el último token y devuelve los logits de clase. Llamar a este método para un modelo de Diffusers lanza NotImplementedError.
shadow = model.base_model.unload_shadow() # a DetachedShadowModel (a PreTrainedModel)
shadow.eval()
# causal LM:
out = shadow.generate(input_ids, max_new_tokens=32)
# sequence classification:
logits = shadow(input_ids=input_ids, attention_mask=attention_mask).logits # (batch, num_labels)Por defecto (copy=False), el modelo devuelto comparte sus módulos con el modelo PEFT, y un backbone de sombra que comparte las incrustaciones de entrada base congeladas los alcanza a través de una referencia que no es un submódulo. Eso está bien para la evaluación, pero significa que save_pretrained escribiría un punto de control sin la tabla de incrustaciones. Pasa copy=True cuando quieras guardar o subir el modelo independiente:
shadow = model.base_model.unload_shadow(copy=True)
shadow.save_pretrained("standalone-shadow")Resumen de las pruebas de rendimiento
API
ShadowConfig
clase peft.ShadowConfig
< source >( task_type: Optional[Union[str, TaskType]] = Nonepeft_type: Optional[Union[str, PeftType]] = Noneauto_mapping: Optional[dict] = Nonepeft_version: Optional[str] = Nonebase_model_name_or_path: Optional[str] = Nonerevision: Optional[str] = Noneinference_mode: bool = Falsetarget_modules: list[str] | str | None = Noneexclude_modules: list[str] | str | None = Noner: int = 8shadow_alpha: float = 0.1shadow_dropout: float = 0.2init_weights: bool = Trueshadow_model: str = 'mirror'shadow_num_hidden_layers: int | None = Noneshadow_hidden_size: int | None = Noneshadow_num_attention_heads: int | None = Noneshadow_intermediate_size: int | None = Noneshare_embeddings: bool = Trueupdate_hidden_size: int | None = Noneauxiliary_loss_weight: float = 0.05layers_to_transform: list[int] | int | None = Nonelayers_pattern: list[str] | str | None = Nonemodules_to_save: list[str] | None = None )
Parámetros
- target_modules (
Optional[Union[list[str], str]]) — Los bloques del transformer que se envolverán con el mecanismo de sombra (bloques completos del decodificador, no capas lineales). Puede ser una lista de nombres de módulos o una expresión regular, p. ej.r'.*\.layers\.\d+$'para apuntar a cada bloque del decodificador. El valor predeterminado esNone, en cuyo caso se envuelven todos los bloques del decodificador del modelo base. Tenga en cuenta que los bloques envueltos deben ser contiguos, ya que el estado de sombra viaja por el bucle del decodificador desde el primer bloque envuelto hasta el último. - exclude_modules (
Optional[Union[list[str], str]]) — Los nombres de los módulos que no se envolverán con el mecanismo de sombra, dados como una lista o una expresión regular. El valor predeterminado esNone. - r (
int) — El rango del cuello de botella de inyección de bajo rangoW_down/W_up. El valor predeterminado es8. - shadow_alpha (
float) — La intensidad de la corrección inyectada que se añade a la entrada del bloque (Ecuación 4). El valor predeterminado es0.1. - shadow_dropout (
float) — La probabilidad de dropout aplicada a la señal de discrepancia antes del cuello de botella (Ec. 3). El valor predeterminado es0.2. - init_weights (
bool) — Si se debe inicializar a ceroW_uppara que la inyección sea una operación sin efecto al inicio del entrenamiento (siguiendo la convención deB=0de LoRA). No cambies este valor a menos que sepas lo que estás haciendo. El valor predeterminado esTrue. - shadow_model (
str) — Cómo construir el backbone de sombra."mirror"(predeterminado) construye un backbone nuevo con la misma arquitectura que el modelo base, pero con menos capas o capas más pequeñas (vea las sobrescriturasshadow_*a continuación). Las arquitecturas de Diffusers con un backend Shadow registrado usan un modelo de arquitectura reducida similar; otros transformadores de difusión compatibles usan un MLP genérico por token como alternativa. Cualquier otra cadena se trata como un id de modelo o una ruta local. Un checkpoint de sombra de Transformers “proyectado” (model_type == "causal_lm_with_hidden_projection", por ejemplo,shadow-llm/Qwen3-0.6B-H8B) carga su backbone preentrenado junto con su proyección entrenada de shadow-hidden -> base-hidden. Los checkpoints explícitos de Diffusers requieren un backend registrado; actualmente se admite Flux2. - shadow_num_hidden_layers (
Optional[int]) — La profundidad del backbone de sombra ("mirror") construido automáticamente. Para Flux2, esto crea el número solicitado de bloques de doble flujo y el doble de bloques de flujo único; para el fallback genérico de Diffusers, es el número de bloques MLP residuales. El valor predeterminado esNone(1capa). - shadow_hidden_size (
Optional[int]) — El tamaño oculto del backbone de sombra construido automáticamente; puede diferir del tamaño oculto base (se inserta una proyección automáticamente). Los backends de arquitectura pueden imponer restricciones adicionales; las reducciones de Flux2 deben preservar su dimensión de cabeza de atención base. El valor predeterminado esNone(igual que el modelo base). - shadow_num_attention_heads (
Optional[int]) — El número de cabezas de atención del backbone de sombra construido automáticamente. El valor predeterminado esNone(igual que el modelo base). Esta opción la utilizan los backends conscientes de la arquitectura, pero no el fallback genérico de MLP de Diffusers. - shadow_intermediate_size (
Optional[int]) — El ancho de la red feed-forward del backbone de sombra construido automáticamente. El valor predeterminado esNone(igual que su tamaño oculto para el reemplazo genérico de MLP de Diffusers). - share_embeddings (
bool) — Si se deben reutilizar las incrustaciones de entrada congeladas de la base para alimentar la columna vertebral de sombra (a través deinputs_embeds) en lugar de la propia tabla de incrustaciones de la columna vertebral de sombra. El valor predeterminado esTrue. - update_hidden_size (
Optional[int]) — El ancho oculto de los MLP de actualizaciónT(candidato) yG(puerta). El valor predeterminado esNone(usar). - auxiliary_loss_weight (
float) — El pesolambdade la pérdida auxiliar de sombra (Ecuación 8-9) que se añade a la pérdida de la tarea cuando se transmitenlabels. Los bucles de entrenamiento de difusión pueden usar el mismo valor para la pérdida de desruido desacoplada. Establezca0para desactivarla. El valor predeterminado es0.05. - layers_to_transform (
Optional[Union[list[int], int]]) — Los índices de bloques a transformar. Si se pasa una lista, el mecanismo de sombra se aplica a los bloques en esos índices. Si se pasa un solo entero, se aplica solo en ese índice. El valor predeterminado esNone(se transforma cada bloque coincidente). - layers_pattern (
Optional[Union[list[str], str]]) — El nombre del patrón de capas, utilizado solo silayers_to_transformes diferente deNone. Este es el nombre delnn.ModuleListque contiene los bloques del decodificador (a menudo"layers"o"h"). El valor predeterminado esNone. - modules_to_save (
Optional[list[str]]) — Los módulos adicionales que se configuran como entrenables y se guardan en el punto de control final (p. ej."lm_head"o una cabeza de clasificador). El valor predeterminado esNone.
Clase de configuración para ShadowModel (ShadowPEFT).
ShadowPEFT aumenta un modelo base decodificador congelado con una pequeña red paralela shadow entrenable. Un esqueleto shadow produce un estado shadow inicial s^(0) que luego recorre el bucle decodificador del modelo base: en cada bloque objetivo, la discrepancia entre los estados ocultos base y el estado shadow se inyecta de nuevo en la entrada del bloque (Ecuación 2-4), y el estado shadow se avanza mediante una actualización residual con puerta desde la salida del bloque (Ecuación 5-7). Solo se entrenan los componentes shadow; el modelo base permanece congelado. Dado que la adaptación es una trayectoria dependiente de la entrada en el espacio de capas y no una diferencia de pesos estática, ShadowPEFT no puede fusionarse en los pesos base.
ShadowModel
clase peft.ShadowModel
< source >( modelpeft_config: Union[PeftConfig, dict[str, PeftConfig]]adapter_name: strlow_cpu_mem_usage: bool = Falsestate_dict: Optional[dict[str, torch.Tensor]] = None )
Crea un modelo ShadowPEFT a partir de un modelo de transformers preentrenado.
ShadowPEFT aumenta un modelo base decodificador congelado con una pequeña red paralela shadow entrenable. Un backbone shadow produce un estado shadow inicial que se integra en el bucle del decodificador base; en cada bloque objetivo, la discrepancia entre los estados ocultos base y el estado shadow se inyecta en el bloque, y el estado shadow se actualiza mediante una actualización residual con compuerta. Solo se entrenan los componentes shadow. Consulte ShadowConfig para la configuración.
El método no puede fusionarse en los pesos base (la adaptación es una trayectoria dependiente de la entrada, no una delta de pesos estática); use ShadowModel.unload_shadow() para obtener la red de sombra independiente en su lugar.
La pérdida de la tarea propia de la ruta de sombra, CE(shadow_head(s^(0)), labels) (sin ponderar; forward aplica el peso).
La pérdida se calcula sobre el estado sombra inicial s^(0) (la salida del backbone sombra, proyectada), exactamente lo que el modelo independiente unload_shadow() calcula como head(projection(backbone(x))). Esto es lo que hace que la red sombra desacoplada sea utilizable por sí sola; entrenarla sobre s^(L) (el estado final, que depende de las salidas por capa del modelo base y no existe de forma independiente) dejaría el modelo desacoplado sin entrenar.
unload_shadow
< source >( adapter_name: str | None = Nonecopy: bool = False )
Parámetros
- adapter_name (
str, opcional) — El adaptador cuya red de sombra se debe descargar. Por defecto, es el adaptador activo. - copy (
bool, opcional, valor predeterminadoFalse) — SiTrue, realiza una copia profunda del modelo devuelto para que sea independiente de este (usa más memoria). SiFalse(valor predeterminado), comparte los módulos, de forma similar amerge_and_unload, que reutiliza los módulos en lugar de clonar los. Modificar un modelo afecta al otro.
Devuelve el backbone de sombra (+ cabeza) como un modelo independiente, sin el modelo base.
El análogo de ShadowPEFT de merge_and_unload: mientras que este devolvería el modelo base con la adaptación integrada, este devuelve solo la red sombra ligera para inferencia de alta eficiencia / en el borde. Ejecuta head(projection(backbone(x))) — las actualizaciones por bloque requieren las salidas base y por lo tanto no existen de forma independiente. Para los modelos de lenguaje, el resultado se comporta como un LM causal normal (soporta generate() y caché de KV). Los modelos de Diffusers no son compatibles porque reconstruir un desruidador completo e independiente es específico de cada arquitectura de difusión.
Asigne el resultado a una variable y úsela; con copy=False, los módulos siguen siendo compartidos con este modelo.
Pasa copy=True cuando tengas la intención de save_pretrained el modelo independiente. Un backbone de sombra que comparte las incrustaciones de entrada congeladas de la base (la configuración predeterminada "mirror") accede a ellas a través de una referencia que no es un submódulo del modelo devuelto, por lo que un punto de control con copy=False carece de la tabla de incrustaciones; copy=True vuelve a adjuntar una copia privada y guarda un punto de control completo.
ShadowCache
Cachés KV emparejados para la decodificación incremental de ShadowPEFT.
La generación autoregresiva requiere una caché del modelo base (claves/valores calculados bajo inyección de sombra) y una caché de modelo de sombra separada (para avanzar s^(0) token a token). Esta es una subclase de Cache de Transformers: las operaciones que afectan al lote o al diseño de la secuencia (reset, reorder, crop, repeat y select) se aplican a ambas cachés, mientras que los metadatos de atención y las actualizaciones se delegan en la caché base. Los atributos de caché desconocidos también se delegan en la caché base para garantizar la compatibilidad con las implementaciones de caché específicas de cada arquitectura.
El wrapper no es compilable de forma intencional, ya que las dos cachés pueden tener disposiciones de capas y tamaños ocultos diferentes. No se admite la conversión de tuplas legacy, ya que una única tupla legacy no puede representar ambas rutas. La mitad sombra se desempaqueta antes del forward base y se vuelve a empaquetar en la salida; consulte los hooks de ShadowModel.