Entrenar un maniquí de jerigonza con una edificación transformadora profunda requiere mucho tiempo. Sin bloqueo, existen técnicas que puedes utilizar para acelerar el entrenamiento. En este artículo, aprenderá sobre:
- Usando
torch.compile()para acelerar el maniquí - Uso de la acumulación de gradiente para entrenar un maniquí con un tamaño de conjunto efectivo longevo
¡Empecemos!
Entrene un maniquí más rápido con torch.compile y acumulación de gradiente
Foto por François Genon. Algunos derechos reservados.
Descripción común
Este artículo se divide en dos partes; ellos son:
- Usando
torch.compile() - Acumulación de gradiente
Usando hachón.compile
Cuando escribe el código de su maniquí y lo ejecuta con PyTorch, el código se ejecuta en modo ansioso. Esto significa que el código se ejecuta bisectriz por bisectriz y los resultados se almacenan en la memoria. Esto es nativo de Python ya que es un jerigonza interpretado. Usted sabe que este es el caso porque cuando comete un error en su código, no verá el error hasta que ejecute esa bisectriz de código.
Ejecutar un maniquí en modo ansioso es calmoso. A partir de PyTorch 2.0, puede utilizar torch.compile() para coleccionar un maniquí para mejorar el rendimiento. Esto genera un nuevo objeto maniquí que está optimizado. No es el mismo objeto maniquí que creó usando nn.Modulepero comparte los mismos tensores con el maniquí innovador. Puede utilizar este maniquí compilado para el paso con destino a delante, el paso con destino a a espaldas y las actualizaciones del optimizador como de costumbre.
Construir un maniquí y compilarlo como un dibujo de cálculo es cómo se suponía que debía funcionar TensorFlow 1.0. Esto dificulta la depuración, ya que el maniquí que ejecuta no puede coincidir bisectriz por bisectriz con el código que escribió. Por lo tanto, no debe coleccionar su maniquí hasta que haya realizado una prueba y haya confirmado que no contiene errores.
No todos los modelos se pueden coleccionar. Sin bloqueo, si su maniquí admite la compilación, se beneficiará inmediatamente de la velocidad. Para coleccionar un maniquí, todo lo que necesita hacer es reemplazar el objeto del maniquí exacto ayer de estar agudo para usarlo:
… maniquí = LlamaForPretraining(model_config).to(dispositivo) model.load_state_dict(punto de control) maniquí = torch.compile(maniquí) …
|
... maniquí = LlamaParaPreentrenamiento(modelo_config).a(dispositivo) maniquí.load_state_dict(control) maniquí = hachón.coleccionar(maniquí) ... |
No cargue los pesos del maniquí luego de la compilación. Esto se debe a que el maniquí compilado es un objeto que comparte los mismos pesos que el maniquí innovador. Durante la compilación, el dibujo de cálculo se construye haciendo narración a los tensores de peso del maniquí innovador. Si carga los pesos luego de la compilación, es posible que el maniquí no funcione como se esperaba.
De modo similar, para desentenderse el maniquí compilado, debe consultar el dictado de estado del maniquí innovador, de la venidero modo:
torch.save(getattr(maniquí, «_orig_mod», maniquí).state_dict(), «model.pth»)
|
hachón.reservar(getattr(maniquí, «_orig_mod», maniquí).dictado_estado(), «maniquí.pth») |
Se puede ingresar al maniquí innovador desde el maniquí compilado usando model._orig_mod. En el código precedente, usamos getattr(model, "_orig_mod", model) para obtener el maniquí innovador si existe, o utilizar model sí mismo si no lo hace. Esta bisectriz de código funciona tanto para modelos compilados como originales.
Acumulación de gradiente
Cuando entrenas a un maniquí, es probable que dediques de dos a tres veces más tiempo al pase con destino a a espaldas que al pase con destino a delante. Esto se debe a que el paso con destino a a espaldas es más intensivo desde el punto de perspicacia computacional y utiliza más memoria.
Un truco sencillo para acelerar el entrenamiento es realizar menos pases con destino a a espaldas. Esto se puede conquistar aumentando el tamaño del conjunto: con la misma cantidad de muestras de datos, un tamaño de conjunto longevo significa menos lotes para procesar.
Sin bloqueo, un tamaño de conjunto longevo requiere más memoria. En un entorno con memoria limitada, puede imitar un tamaño de conjunto longevo ejecutando múltiples pases con destino a delante y acumulando los gradientes. esto se ardor acumulación de gradiente.
Es más practicable explicar esta idea con código:
.. acumular_pasos = 4 para época en rango(num_epochs): optimizador.zero_grad() para i, conjunto en enumerar(cargador de datos): # obtener datos por lotes input_ids, target_ids = conjunto # crear máscara de atención: máscara causal + máscara de relleno attn_mask = create_causal_mask(input_ids.shape(1), dispositivo) + create_padding_mask(input_ids, PAD_TOKEN_ID, dispositivo) # extraer la salida del maniquí logits = model(input_ids, attn_mask) # calcular la pérdida: entropía cruzada entre logits y el objetivo, ignorando los tokens de relleno loss = loss_fn(logits.view(-1, logits.size(-1)), target_ids.view(-1)) loss = loss / acumular_steps # Ejecutar con destino a a espaldas, pero desempolvar solo una vez cada `accumulate_steps` pasos pérdida.backward() si (i + 1) % acumular_steps == 0: torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizador.step() optimizador.zero_grad() planificador.paso()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
.. acumular_pasos = 4 para época en rango(num_épocas): optimizador.cero_grado() para i, conjunto en enumerar(cargador de datos): # obtener datos por lotes ids_entrada, identificadores_objetivos = conjunto # crear máscara de atención: máscara causal + máscara de relleno atención_mascarilla = crear_mascarilla_causal(ids_entrada.forma(1), dispositivo) + crear_padding_mask(ids_entrada, PAD_TOKEN_ID, dispositivo) # extraer la salida del maniquí logits = maniquí(ids_entrada, atención_mascarilla) # pérdida de cálculo: entropía cruzada entre logits y el objetivo, ignorando los tokens de relleno pérdida = pérdida_fn(logits.perspicacia(–1, logits.tamaño(–1)), identificadores_objetivos.perspicacia(–1)) pérdida = pérdida / acumular_pasos # Ejecutar con destino a a espaldas, pero desempolvar solo una vez cada paso de `accumulate_steps` pérdida.con destino a a espaldas() si (i + 1) % acumular_pasos == 0: hachón.nn.utiles.clip_grad_norm_(maniquí.parámetros(), 1.0) optimizador.paso() optimizador.cero_grado() planificador.paso() |
El ciclo de entrenamiento precedente es un extracto del artículo precedente para entrenar un maniquí Candela en su GPU regional.
Normalmente, cuando ejecutas un pase con destino a delante, calculas la pérdida. Entonces llamas loss.backward() para propagar con destino a a espaldas el gradiente de pérdida a través de los parámetros del maniquí. En PyTorch, el backward() El método es acumulativo, lo que significa que los gradientes se suman. Por lo tanto, es necesario aldabear optimizer.zero_grad() explícitamente para borrar los gradientes ayer de ejecutar el pase con destino a a espaldas.
En el código precedente, deliberadamente no llamas optimizer.zero_grad() en cada iteración. En su lado, ejecuta la retropropagación de la pérdida dividida por accumulate_steps. De esta modo, los gradientes se reducen pero se acumulan a lo dispendioso accumulate_steps iteraciones. Una vez cada accumulate_steps iteraciones, ejecuta el optimizador para ajustar los parámetros del maniquí.
Este enfoque produce resultados comparables al uso de un tamaño de conjunto longevo. Sin bloqueo, hexaedro que ejecuta menos actualizaciones del optimizador, el software de tasa de educación debe ajustarse en consecuencia. Esto significa que debe inicializar el programador con una cantidad diferente de pasos:
… num_training_steps = (len(cargador de datos) // acumular_pasos) * num_epochs cosine_scheduler = lr_scheduler.CosineAnnealingLR( optimizador, T_max=num_training_steps – num_warmup_steps, eta_min=0 )
|
... num_pasos_de_formación = (len(cargador de datos) // acumular_pasos) * num_epochs programador_coseno = lr_programador.CosenoRecocidoLR( optimizador, T_máx.=num_pasos_de_formación – num_pasos_de_calentamiento, eta_min=0 ) |
Lección adicional
A continuación se muestran algunos materiales que pueden resultarle interesantes:
Recopilación
En este artículo, aprendiste que usar torch.compile() Puede ayudarle a acelerar el maniquí compilando el dibujo de cálculo. Incluso aprendió que la acumulación de gradientes es una técnica para entrenar con un tamaño de conjunto efectivo longevo mediante la acumulación de gradientes de múltiples minilotes. Hexaedro que de esta modo ejecuta menos actualizaciones del optimizador, ahorra tiempo en pasos con destino a a espaldas y actualizaciones de parámetros.