Consejos y dificultades de configuración de la biblioteca de paralelismo de modelos SageMaker distribuidos - Amazon SageMaker

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Consejos y dificultades de configuración de la biblioteca de paralelismo de modelos SageMaker distribuidos

Revisa los siguientes consejos y dificultades antes de usar la biblioteca de paralelismo SageMaker de modelos de Amazon. Esta lista incluye sugerencias que se aplican en todos los marcos. Para obtener consejos PyTorch específicos, consulte TensorFlow Modifica un script de entrenamiento TensorFlow y, respectivamente. Modifique un guion PyTorch de entrenamiento

Tamaño del lote y número de microlotes

  • La biblioteca es más eficiente cuando aumenta el tamaño del lote. En los casos de uso en los que el modelo cabe dentro de un solo dispositivo, pero solo se puede entrenar con un tamaño de lote pequeño, el tamaño del lote puede y debe aumentarse después de integrar la biblioteca. El paralelismo de modelos ahorra memoria para modelos grandes, lo que le permite entrenar utilizando tamaños de lote que antes no cabían en la memoria.

  • Elegir varios microlotes que sean demasiado pequeños o demasiado grandes puede reducir el rendimiento. La biblioteca ejecuta cada microlote secuencialmente en cada dispositivo, por lo que el tamaño del microlote (tamaño del lote dividido por el número de microlotes) debe ser lo suficientemente grande como para utilizar completamente cada GPU. Al mismo tiempo, la eficiencia de la canalización aumenta con el número de microlotes, por lo que es importante lograr el equilibrio correcto. Normalmente, un buen punto de partida consiste en probar 2 o 4 microlotes, aumentar el tamaño de los lotes hasta el límite de memoria y, a continuación, experimentar con grandes tamaños de lote y números de microlotes. A medida que aumenta el número de microlotes, los tamaños de lotes más grandes podrían ser factibles si se utiliza una canalización intercalada.

  • El tamaño del lote debe ser siempre divisible por el número de microlotes. Tenga en cuenta que, dependiendo del tamaño del conjunto de datos, a veces el último lote de cada época puede ser de menor tamaño que el resto, y este lote más pequeño también debe ser divisible por el número de microlotes. Si no es así, puede configurar drop_remainder=True la tf.Dataset.batch() llamada (entrada TensorFlow) o configurar la drop_last=True entrada DataLoader (entrada PyTorch) para que no se utilice este último lote pequeño. Si utiliza una API diferente para la canalización de datos, es posible que deba omitir manualmente el último lote siempre que no sea divisible por el número de microlotes.

Partición manual

  • Si utiliza la partición manual, tenga en cuenta los parámetros que consumen varias operaciones y módulos del modelo, como la tabla de incrustación en arquitecturas de transformadores. Los módulos que comparten el mismo parámetro deben colocarse en el mismo dispositivo para que sean correctos. Cuando se utiliza la partición automática, la biblioteca aplica automáticamente esta restricción.

Preparación de datos

  • Si el modelo toma varias entradas, asegúrese de inicializar las operaciones aleatorias de su canalización de datos (por ejemplo, mezclar) con smp.dp_rank(). Si el conjunto de datos se está fragmentando determinísticamente en dispositivos de paralelismo de datos, asegúrese de que el fragmento esté indexado por smp.dp_rank(). De este modo se asegurará de que el orden de los datos que se ven en todos los rangos que forman una partición de modelo sea coherente.

Devolver tensores desde smp.DistributedModel

  • Cualquier tensor devuelto por la función smp.DistributedModel.call (for TensorFlow) o smp.DistributedModel.forward (for PyTorch) se transmite a todos los demás rangos, desde el rango que calculó ese tensor en particular. Como resultado, no debe devolverse cualquier tensor que no sea necesario fuera de los métodos de llamada y reenvío (activaciones intermedias, por ejemplo), ya que esto provoca una comunicación innecesaria y sobrecarga de memoria y perjudica el rendimiento.

El decorador @smp.step

  • Si una función decorada por smp.step tiene un argumento tensor que no tiene una dimensión por lotes, el nombre del argumento debe proporcionarse en la lista non_split_inputs al llamarsmp.step. Esto evita que la biblioteca intente dividir el tensor en microlotes. Para obtener más información, consulte smp.step en la documentación de las API.

Retraso de la inicialización de parámetros

En el caso de modelos muy grandes con más de 100 000 millones de parámetros, la inicialización del peso a través de la memoria de la CPU puede provocar un error. out-of-memory Para evitar esto, la biblioteca ofrece el gestor de contexto de smp.delay_param_initialization. Esto retrasa la asignación física de los parámetros hasta que se mueven a la GPU durante la primera ejecución de una función decorada por smp.step. Esto evita el uso innecesario de memoria de la CPU durante la inicialización del entrenamiento. Utilice el gestor de contexto cuando cree un objeto modelo como se muestra en el siguiente código.

with smp.delay_param_initialization(enabled=True): model = MyModel()

Paralelismo tensorial para PyTorch

  • Si está utilizando un valor de inicialización para obtener resultados deterministas, establezca el valor de inicialización basándose en smp.dp_rank() (por ejemplo, torch.manual_seed(42 + smp.dp_rank())). Si no lo hace, diferentes particiones de un nn.Parameter se inicializan de la misma manera, lo que afecta a la convergencia.

  • SageMakerLa biblioteca de paralelismo de modelos utiliza NCCL para implementar los colectivos necesarios para la distribución de los módulos. Especialmente en los modelos más pequeños, si se programan demasiadas llamadas NCCL en la GPU al mismo tiempo, el uso de memoria podría aumentar debido al espacio adicional utilizado por NCCL. Para contrarrestar esto, smp limita las llamadas de NCCL de modo que el número de operaciones de NCCL en curso en un momento dado sea inferior o igual a un límite determinado. El límite predeterminado es 8, pero se puede ajustar mediante la variable de entorno SMP_NCCL_THROTTLE_LIMIT. Si observa más uso de memoria del que espera mientras utiliza el paralelismo de tensores, puede intentar reducir este límite. Sin embargo, elegir un límite demasiado pequeño podría provocar una pérdida de rendimiento. Para deshabilitar la limitación por completo, puede configurar SMP_NCCL_THROTTLE_LIMIT=-1.

  • La siguiente identidad, que se mantiene cuando el grado de paralelismo tensor es 1, no se mantiene cuando el grado de paralelismo tensor es superior a 1: smp.mp_size() * smp.dp_size() == smp.size(). Esto se debe a que el grupo paralelo de tensores forma parte del grupo paralelismo modelo y del grupo de paralelismo de datos. Si tu código tiene referencias existentes a mp_rank, mp_size, MP_GROUP, etc., y si desea trabajar solo con el grupo paralelo de canalización, es posible que tenga que reemplazar las referencias por smp.pp_size(). Las siguientes identidades son siempre ciertas:

    • smp.mp_size() * smp.rdp_size() == smp.size()

    • smp.pp_size() * smp.dp_size() == smp.size()

    • smp.pp_size() * smp.tp_size() * smp.rdp_size() == smp.size()

  • Dado que el encapsulador smp.DistributedModel modifica los parámetros del modelo cuando el paralelismo de tensores está habilitado, el optimizador debe crearse después de llamar a smp.DistributedModel, con los parámetros distribuidos. Por ejemplo, lo siguiente no funciona:

    ## WRONG model = MyModel() optimizer = SomeOptimizer(model.parameters()) model = smp.DistributedModel(model)  # optimizer now has outdated parameters! 

    En su lugar, el optimizador debe crearse con los parámetros del smp.DistributedModelde la siguiente manera:

    ## CORRECT model = smp.DistributedModel(MyModel()) optimizer = SomeOptimizer(model.optimizers())
  • Cuando un módulo se sustituye por su contraparte distribuida a través del paralelismo de tensores, el módulo distribuido no hereda sus pesos del módulo original e inicializa nuevos pesos. Esto significa que, por ejemplo, si es necesario inicializar las ponderaciones en una llamada concreta (por ejemplo, a través de una llamada load_state_dict), esto debe suceder después de la llamada smp.DistributedModel, una vez que tenga lugar la distribución del módulo.

  • Al acceder directamente a los parámetros de los módulos distribuidos, tenga en cuenta que el peso no tiene la misma forma que el módulo original. Por ejemplo, 

    with smp.tensor_parallelism():     linear = nn.Linear(60, 60) # will pass assert tuple(linear.weight.shape) == (60, 60) distributed_linear = smp.DistributedModel(linear) # will fail. the number of input channels will have been divided by smp.tp_size() assert tuple(distributed_linear.module.weight.shape) == (60, 60)
  • Se recomienda encarecidamente usar torch.utils.data.distributed.DistributedSampler para el paralelismo de tensores. Esto garantiza que cada clasificación paralela de datos reciba el mismo número de muestras de datos, lo que evita los bloqueos que podrían resultar de diferentes dp_rank está tomando un número diferente de pasos.

  • Si utiliza la join API PyTorch de la DistributedDataParallel clase para gestionar casos en los que diferentes rangos paralelos de datos tienen diferentes números de lotes, aún debe asegurarse de que los rangos que están en el mismo nivel TP_GROUP tengan el mismo número de lotes; de lo contrario, los colectivos de comunicación utilizados en la ejecución distribuida de los módulos podrían bloquearse. Rangos que están en diferentes TP_GROUPs pueden tener diferentes números de lotes, siempre que se utilice la API join.

  • Si desea controlar el modelo y utilizar el paralelismo de tensores, tenga en cuenta lo siguiente:

    • Para evitar el estancamiento y las condiciones de carrera mientras se guardan y cargan modelos cuando utiliza el paralelismo de tensores, asegúrese de llamar a las funciones adecuadas desde los siguientes estados de modelo y optimizador dentro de un rango de paralelismo de datos reducido.

    • Si está haciendo la transición de un script de paralelismo de canalización existente y habilitando el tensor de paralelismo para el script, asegúrese de modificar cualquier bloque if smp.dp_rank() == 0 utilizado para guardar y cargar con bloques if smp.rdp_rank() == 0. De lo contrario, podría hacer que tu trabajo de entrenamiento se detenga.

    Para obtener más información sobre el punto de control de un modelo con paralelismo de tensores, consulte Punto de control de un modelo distribuido.