PEFT documentation
Conversión de LoRA
Conversión de LoRA
Funciones que permiten convertir modelos PEFT no LoRA en modelos LoRA.
Descripción
PEFT admite decenas de técnicas diferentes de ajuste fino eficiente en parámetros. La más popular, con diferencia, es LoRA. Esto significa que muchos otros paquetes también admiten LoRA. Por ejemplo, Diffusers permite cargar adaptadores LoRA para cambiar las capacidades de los modelos de difusión. vLLM permite servir modelos con adaptadores LoRA. Esto es bueno, pero lamentablemente, todos los demás métodos PEFT, distintos de LoRA, rara vez están soportados. Por lo tanto, incluso si otro método PEFT funcionara mejor para su caso de uso específico, es posible que no pueda utilizarlo porque los paquetes aguas abajo no ofrecen soporte.
Aquí presentamos una solución potencial. PEFT ofrece dos funciones, save_as_lora() y convert_to_lora(), que permiten convertir un adaptador PEFT en un adaptador LoRA. Por ahora, no todos los métodos de PEFT admiten esto, pero si lo hacen, significa que puedes comenzar con el método de PEFT que mejor funcione para ti y luego utilizarlo como si fuera un adaptador LoRA.
Ejemplo
El rango de LoRA del adaptador convertido puede fijarse en un rango fijo pasando un int > 0 al argumento rank, o en un rango dinámico, que se adapta a cada capa, pasando un float entre 0 y 1 al argumento rank. Los rangos dinámicos pueden ser más eficientes (mismo rendimiento con menos parámetros).
Rango de LoRA fijo
El uso de save_as_lora() es relativamente sencillo:
from peft import get_peft_model, save_as_lora
# first load and train your non-LoRA PEFT model as normal
base_model = ...
non_lora_config = ...
model = get_peft_model(base_model, non_lora_config)
# check that this PEFT method can indeed be converted to LoRA
assert model.supports_lora_conversion()
... # train the model
# the rank of the LoRA adapter that you want to convert to
target_rank = 64
# save as a LoRA checkpoint
save_as_lora(output_path, model, rank=target_rank)Esto creará un checkpoint de LoRA en output_path que puedes cargar como cualquier otro adaptador de LoRA, o usar en paquetes de nivel inferior como Diffusers o vLLM.
La función convert_to_lora() es útil si no deseas guardar el adaptador LoRA convertido, sino usar los pesos convertidos de inmediato, por ejemplo, para realizar evaluaciones:
from peft import convert_to_lora, get_peft_model, set_peft_model_state_dict
base_model = ...
non_lora_config = ...
model = get_peft_model(base_model, non_lora_config)
... # train the model
# get the lora config and state dict of the converted lora model
lora_config, lora_state_dict = convert_to_lora(model, rank=target_rank)
# reload the base model, or use model.unload()
base_model = ...
# apply the lora config to the base model
lora_model = get_peft_model(base_model, lora_config)
# load the LoRA weights onto the base model
set_peft_model_state_dict(lora_model, state_dict)Rango dinámico de LoRA
En los ejemplos anteriores, utilizamos un rango de LoRA fijo para la conversión. Sin embargo, es concebible que algunas capas no requieran un rango alto para ser convertidas con precisión, mientras que otras capas requieren un rango más alto. Para adaptarse a esto, PEFT ofrece la opción de pasar un número flotante entre 0 y 1 como argumento rank. Supongamos que pasas rank=0.5. Esto significa que, para cada capa, el rango del adaptador LoRA se elige de tal manera que el adaptador LoRA explique el 50% de la varianza en los pesos introducida por el adaptador original. En términos más técnicos, internamente realizamos una descomposición en valores singulares sobre la contribución de pesos del adaptador y luego tomamos los valores singulares superiores que, al normalizarse, suman el valor pasado.
# set a dynamic rank by passing a float
threshold = 0.7
# save as a LoRA checkpoint
save_as_lora(output_path, model, rank=threshold)
# get the lora config and state dict directly:
lora_config, lora_state_dict = convert_to_lora(model, rank=threshold)
# inspect the different ranks per layer:
print(lora_config.rank_pattern)El uso de este tipo de rango dinámico de LoRA puede ser útil si la contribución de las diferentes capas varía mucho. La desventaja es que podría implicar que algunas capas tengan un rango de LoRA muy alto, lo que puede provocar picos de memoria. Pruebe qué funciona mejor para su caso de uso.
Compilar el modelo
Para modelos grandes, la conversión puede tardar un tiempo; por ejemplo, cada módulo PEFT debe pasar por un cálculo de SVD. Al pasar compile_kwargs a save_as_lora() o convert_to_lora(), puedes aplicar torch.compile a la función de conversión y acelerar potencialmente el proceso. compile_kwargs es un diccionario de argumentos de palabra clave que se pasan a torch.compile (un diccionario vacío también funciona). A continuación se muestra un ejemplo:
compile_kwargs = {"dynamic": True, "mode": "max-autotune-no-cudagraphs", "fullgraph": True}
save_as_lora(output_path, model, rank=rank, compile_kwargs=compile_kwargs)Conversión de LoRA a LoRA
También es posible convertir un adaptador LoRA en otro adaptador LoRA. ¿Por qué querrías hacer eso? Hay una razón: si deseas reducir el rango del adaptador LoRA. Si, después del entrenamiento, quieres reducir el tamaño del adaptador LoRA, usa save_as_lora() o convert_to_lora() y pasa un rango menor. Esto te dará un nuevo adaptador LoRA con una huella de memoria y almacenamiento más pequeña.
Métricas
Conversión de no-LoRA a LoRA
Por supuesto, convertir un adaptador PEFT en otro adaptador es un proceso con pérdida de información. Es muy probable que el nuevo adaptador no rinde igual de bien que el adaptador inicial. Por lo tanto, se recomienda encarecidamente evaluar el adaptador LoRA convertido. De esta manera, puede asegurarse de que el adaptador convertido rinde lo suficientemente bien para su caso de uso. Se aplica la regla general de que, cuanto mayor sea el rango del adaptador LoRA, mejor aproximará su adaptador inicial. Esto significa que el adaptador LoRA convertido puede requerir más parámetros que el adaptador original para lograr un rendimiento similar.
A modo de ejemplo, a continuación se muestran algunos datos derivados del PEFT MetaMathQA benchmark. Para ello, se utilizó un adaptador LoHa para afinar meta-llama/Llama-3.2-3B en MetaMathQA y se evaluó en GSM8K. El adaptador LoKr inicial tenía un rango de 32, lo que resultó en 18.350.080 parámetros entrenables y una precisión de prueba del 41,85 %. La evaluación requirió 12,25 GB de memoria. El punto de control se convirtió en LoRA con diferentes valores para rank. El resultado obtenido es:
| rank | parámetros entrenables | precisión de prueba (%) | cambio en la precisión | memoria reservada (máx., GB) | aumento de memoria |
|---|---|---|---|---|---|
| 8 | 2293760 | 37.60 | -4.25 | 12.41 | 0.16 |
| 16 | 4587520 | 38.89 | -2.96 | 12.15 | -0.10 |
| 32 | 9175040 | 40.11 | -1.74 | 12.41 | 0.16 |
| 64 | 18350080 | 39.20 | -2.65 | 12.18 | -0.07 |
| 0.4 | 2428928 | 37.60 | -4.25 | 12.41 | 0.16 |
| 0.5 | 4761600 | 40.18 | -1.67 | 12.41 | 0.16 |
| 0.6 | 8857600 | 39.42 | -2.43 | 12.41 | 0.16 |
| 0.7 | 16230400 | 39.04 | -2.81 | 12.15 | -0.10 |
Como puede verse, es posible alcanzar una precisión de prueba cercana a la del adaptador LoHa original si el rango es lo suficientemente alto. Elegir el rango adecuado implica un equilibrio entre el rendimiento del modelo y su eficiencia. Para reproducir este experimento, siga el script en https://github.com/huggingface/peft/tree/main/scripts/evaluate-lora-conversion.py.
Tenga en cuenta que el número de parámetros entrenables no se puede traducir de forma directa al uso de memoria. Algunos métodos de PEFT requieren más memoria, otros menos, incluso con el mismo número de parámetros entrenables. Por lo tanto, incluso si, tras la conversión, el adaptador LoRA tiene más parámetros que el original, podría seguir siendo más eficiente en memoria durante el servicio.
Conversión de LoRA a LoRA
De manera similar al experimento anterior, también podemos evaluar la conversión de LoRA a LoRA (es decir, la compresión de LoRA). Aquí, partimos de un adaptador LoRA de rango 64 entrenado con la misma configuración que arriba, utilizando RS-LoRA. El adaptador inicial tiene 18.350.080 parámetros entrenables, una precisión de prueba del 52,92 % y requiere 12,58 GB de memoria para la evaluación. La siguiente tabla muestra los resultados de convertir este adaptador en adaptadores LoRA de rango menor:
| rank | parámetros entrenables | precisión de prueba (%) | cambio en la precisión | memoria reservada (máx., GB) | aumento de memoria |
|---|---|---|---|---|---|
| 8 | 2293760 | 43.37 | -9.55 | 12.38 | -0.20 |
| 16 | 4587520 | 48.90 | -4.02 | 12.38 | -0.20 |
| 32 | 9175040 | 51.48 | -1.44 | 12.49 | -0.09 |
| 48 | 13762560 | 52.01 | -0.91 | 12.38 | -0.20 |
| 0.5 | 2150400 | 44.12 | -8.80 | 12.37 | -0.21 |
| 0.6 | 3082240 | 47.54 | -5.38 | 12.37 | -0.21 |
| 0.7 | 4448256 | 50.49 | -2.43 | 12.37 | -0.21 |
| 0.8 | 6510592 | 50.11 | -2.81 | 12.37 | -0.21 |
| 0.9 | 10022912 | 51.55 | -1.37 | 12.38 | -0.20 |
| 0.95 | 12976128 | 52.62 | -0.30 | 12.39 | -0.19 |
Por ejemplo, para un rango de 0.95, podemos reducir la brecha de precisión a solo 0.3 puntos porcentuales mientras disminuimos el número de parámetros en un 30%. También ten en cuenta que estos LoRAs comprimidos pueden ser mejores que entrenarlos directamente en el rango inferior: por ejemplo, para un rango de 32, el entrenamiento directo resulta en una precisión de prueba del 48.22%, mientras que la conversión desde un rango de 64 resulta en un 51.48%.
Precauciones
Existen algunas limitaciones en la conversión de LoRA. Como se mencionó anteriormente, se espera una reducción en el rendimiento y el LoRA convertido probablemente será menos eficiente en parámetros que el adaptador original. Además, la conversión de LoRA tiene estas limitaciones:
- Por ahora, solo se pueden convertir los adaptadores aplicados a capas lineales.
- No todos los métodos de PEFT admiten actualmente la conversión a LoRA.
Si hay mucha demanda para extender la conversión de LoRA, háganoslo saber creando una discusión en GitHub y lo haremos funcionar con más tipos de capas y métodos PEFT.
API
Convierte un modelo no-LoRA en un modelo LoRA, devuelve el LoraConfig y el state_dict
peft.convert_to_lora
< source >( model: Modulerank: floatadapter_name: str = 'default'progressbar: bool = Falsecompile_kwargs = None )
Parámetros
- model — El modelo que se va a convertir. Debe ser un modelo que tenga capas PEFT que admitan la conversión.
- rank (
intorfloat) — El rango deseado para el adaptador LoRA devuelto. Un rango más alto produce un adaptador LoRA que refleja con mayor precisión el adaptador original. Sin embargo, también requerirá más memoria, cómputo y espacio en disco. Por lo tanto, elija un valor que represente el mejor equilibrio para su caso de uso y valide el adaptador final. Si se pasa un float, se interpreta como un umbral de varianza explicada / energía: elegimos el rango k más pequeño tal que los k valores singulares superiores representen al menos esa fracción de la suma de los cuadrados de los valores singulares. Esto resulta efectivamente en rangos más bajos si unos pocos valores singulares pueden capturar la adaptación de esta capa. Un float más bajo significa que el rango es más bajo y viceversa. Tenga en cuenta que los rangos dinámicos pueden llevar a rangos muy desiguales por capa, lo que significa que algunas capas pueden requerir una cantidad desproporcionadamente alta de memoria para las activaciones. Elegir un rango fijo (int) es mejor para lograr un requisito de memoria predecible. - adapter_name (
str, opcional) — El nombre del adaptador que se va a convertir. Solo se puede convertir un adaptador a la vez. El valor predeterminado es"default". - progressbar (
bool) — si se muestra una barra de progreso que indica el avance de la conversión (puede tardar unos minutos en el caso de modelos grandes). - compile_kwargs (
dict, opcional) — Si se proporciona, compila la función para convertir los módulos individuales a LoRA, pasando los kwargs indicados atorch.compile. Esto puede acelerar potencialmente la conversión en modelos grandes.
Convierta un modelo no-LoRA con capas de PEFT en un punto de control LoRA.
Esto solo se admite para algunos métodos específicos de PEFT que permiten una conversión equivalente. En esencia, se trata de métodos de PEFT que funcionan actualizando el peso base con un peso delta. Además, por ahora, solo se admiten capas lineales.
El adaptador LoRA intentará aproximar el adaptador inicial lo más cerca posible. A mayor rango, mejor será la aproximación. Se espera que la aproximación nunca alcance el rendimiento completo del adaptador original y que la eficiencia de parámetros del adaptador LoRA sea menor que la del adaptador original (es decir, para un rendimiento similar, requerirá más parámetros). La conversión aún puede ser útil en muchas situaciones:
- En PEFT, LoRA admite más funciones que la mayoría de los otros métodos, por ejemplo, lotes de adaptadores mixtos. Por lo tanto, el adaptador convertido puede usarse con esas funciones.
- Algunos paquetes de nivel inferior admiten adaptadores LoRA, pero no otros métodos PEFT, por ejemplo, Diffusers. La conversión permite utilizar un adaptador que no sea LoRA con esos paquetes.
El factor de escalado de LoRA ya está integrado en los pesos de LoRA, por lo que la escalación siempre será uno (es decir, el rango y alpha se eligen para que sean idénticos).
Nota: Esta función no admite modelos fragmentados (aún).
Lanza
TypeError:
Si el modelo proporcionado no tiene capas que puedan convertirse a LoRA, se lanza un TypeError.
ValueError:
Si se eligió un rango no válido (demasiado alto o demasiado bajo).
Convierte un modelo no-LoRA en un modelo LoRA, guarda el checkpoint del adaptador y la configuración en la ruta indicada
peft.save_as_lora
< source >( path: str | os.PathLikemodel: Modulerank: floatadapter_name: str = 'default'progressbar: bool = Falsecompile_kwargs = None )
Parámetros
- model — El modelo que se va a convertir. Debe ser un modelo que tenga capas PEFT que admitan la conversión.
- rank (
intorfloat) — El rango deseado para el adaptador LoRA devuelto. Un rango más alto produce un adaptador LoRA que refleja con mayor precisión el adaptador original. Sin embargo, también requerirá más memoria, cómputo y espacio en disco. Por lo tanto, elija un valor que represente el mejor equilibrio para su caso de uso y valide el adaptador final. Si se pasa un float, se interpreta como un umbral de varianza explicada / energía: elegimos el rango k más pequeño tal que los k valores singulares superiores representen al menos esa fracción de la suma de los cuadrados de los valores singulares. Esto resulta efectivamente en rangos más bajos si unos pocos valores singulares pueden capturar la adaptación de esta capa. Un float más bajo significa que el rango es más bajo y viceversa. Tenga en cuenta que los rangos dinámicos pueden llevar a rangos muy desiguales por capa, lo que significa que algunas capas pueden requerir una cantidad desproporcionadamente alta de memoria para las activaciones. Elegir un rango fijo (int) es mejor para lograr un requisito de memoria predecible. - adapter_name (
str, opcional) — El nombre del adaptador que se va a convertir. Solo se puede convertir un adaptador a la vez. El valor predeterminado es"default". - progressbar (
bool) — si se muestra una barra de progreso que indica el avance de la conversión (puede tardar unos minutos en el caso de modelos grandes). - compile_kwargs (
dict, opcional) — Si se proporciona, compila la función para convertir los módulos individuales a LoRA, pasando los kwargs indicados atorch.compile. Esto puede acelerar potencialmente la conversión en modelos grandes.
Convierta un modelo no-LoRA con capas PEFT a LoRA y luego guarde el archivo de checkpoint y la configuración PEFT.
Esto solo se admite para algunos métodos específicos de PEFT que permiten una conversión equivalente. En esencia, se trata de métodos de PEFT que funcionan actualizando el peso base con un peso delta. Además, por ahora, solo se admiten capas lineales.
El adaptador LoRA intentará aproximar el adaptador inicial lo más cerca posible. A mayor rango, mejor será la aproximación. Se espera que la aproximación nunca alcance el rendimiento completo del adaptador original y que la eficiencia de parámetros del adaptador LoRA sea menor que la del adaptador original (es decir, para un rendimiento similar, requerirá más parámetros). La conversión aún puede ser útil en muchas situaciones:
- En PEFT, LoRA admite más funciones que la mayoría de los otros métodos, por ejemplo, lotes de adaptadores mixtos. Por lo tanto, el adaptador convertido puede usarse con esas funciones.
- Algunos paquetes de nivel inferior admiten adaptadores LoRA, pero no otros métodos PEFT, por ejemplo, Diffusers. La conversión permite utilizar un adaptador que no sea LoRA con esos paquetes.
El factor de escalado de LoRA ya está integrado en los pesos de LoRA, por lo que la escalación siempre será uno (es decir, el rango y alpha se eligen para que sean idénticos).
Puedes cargar el peso LoRA convertido de esta manera:
>>> lora_path = ...
>>> save_as_lora(lora_path, model, rank=...)
>>> base_model = AutoModel.from_pretrained(...)
>>> lora_model = PeftModel.from_pretrained(base_model, lora_path)Nota: Esta función no admite modelos fragmentados (aún).
Lanza
TypeError:
Si el modelo proporcionado no tiene capas que puedan convertirse a LoRA, se lanza un TypeError.
ValueError:
Si se eligió un rango no válido (demasiado alto o demasiado bajo).