Suggerimenti e insidie per la configurazione della SageMaker Distributed Model Parallelism Library - Amazon SageMaker

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Suggerimenti e insidie per la configurazione della SageMaker Distributed Model Parallelism Library

Consulta i seguenti suggerimenti e insidie prima di utilizzare la libreria di parallelismo SageMaker dei modelli di Amazon. Questo elenco include suggerimenti applicabili a tutti i framework. Per TensorFlow suggerimenti PyTorch specifici, vedi Modificare uno script di addestramento TensorFlow eModificare uno script PyTorch di addestramento, rispettivamente.

Dimensione del batch e numero di microbatch

  • La libreria è più efficiente quando la dimensione del batch viene aumentata. Nei casi d'uso in cui il modello si inserisce in un singolo dispositivo, ma può essere addestrato solo con batch di piccole dimensioni, la dimensione del batch può e deve essere aumentata dopo l'integrazione della libreria. Il parallelismo dei modelli consente di risparmiare memoria per i modelli di grandi dimensioni, consentendovi di addestrare utilizzando batch di dimensioni che in precedenza non rientravano nella memoria.

  • La scelta di un numero di microbatch troppo piccolo o troppo grande può ridurre le prestazioni. La libreria esegue ogni microbatch in sequenza in ogni dispositivo, pertanto la dimensione del microbatch (dimensione del batch divisa per il numero di microbatch) deve essere sufficientemente grande da consentire l'utilizzo completo di ciascuna GPU. Allo stesso tempo, l'efficienza della pipeline aumenta con il numero di microbatch, quindi è importante trovare il giusto equilibrio. In genere, un buon punto di inizio è provare 2 o 4 microbatch, aumentando la dimensione del batch fino al limite di memoria, e quindi sperimentare con batch di dimensioni e numeri di microbatch più grandi. Con l'aumento del numero di microbatch, potrebbero diventare possibili lotti di dimensioni maggiori se si utilizza una pipeline interlacciata.

  • La dimensione del batch deve essere sempre divisibile per il numero di microbatch. Tieni presente che, a seconda delle dimensioni del set di dati, a volte l'ultimo batch di ogni epoca può avere dimensioni inferiori rispetto al resto e questo batch più piccolo deve essere divisibile anche per il numero di microbatch. drop_remainder=TrueIn caso contrario, è possibile impostare la tf.Dataset.batch() chiamata (in TensorFlow) o impostare DataLoader (drop_last=Truein PyTorch), in modo che quest'ultimo, piccolo batch non venga utilizzato. Se si utilizza un'API diversa per la pipeline di dati, potrebbe essere necessario saltare manualmente l'ultimo batch ogni volta che non è divisibile per il numero di microbatch.

Partizionamento manuale

  • Se utilizzi il partizionamento manuale, tieni presente i parametri utilizzati da più operazioni e moduli del modello, come la tabella di incorporamento nelle architetture dei trasformatori. I moduli che condividono lo stesso parametro devono essere collocati nello stesso dispositivo per motivi di correttezza. Quando viene utilizzato il partizionamento automatico, la libreria applica automaticamente questo vincolo.

Preparazione dei dati

  • Se il modello richiede più input, assicurati di impostare le operazioni casuali nella data pipeline (ad esempio, lo shuffling) con smp.dp_rank(). Se il set di dati viene suddiviso in modo deterministico su dispositivi di parallelismo dei dati, assicurati che lo shard sia indicizzato da smp.dp_rank(). Questo serve a garantire che l'ordine dei dati visualizzati su tutte le classificazioni che formano una partizione del modello sia coerente.

Restituzione di tensori da smp.DistributedModel

  • Qualsiasi tensore restituito dalla funzione smp.DistributedModel.call (for TensorFlow) o smp.DistributedModel.forward (for PyTorch) viene trasmesso a tutti gli altri ranghi, dal rango che ha calcolato quel particolare tensore. Di conseguenza, qualsiasi tensore non necessario al di fuori dei metodi call e forward (attivazioni intermedie, ad esempio) non dovrebbe essere restituito, poiché ciò causa inutili comunicazioni e sovraccarico di memoria e danneggia le prestazioni.

Il decoratore @smp.step

  • Se una funzione smp.step decorata ha un argomento di tensore che non ha una dimensione batch, il nome dell'argomento deve essere fornito nell'elenco non_split_inputsdurante la chiamata smp.step. Ciò impedisce alla libreria di tentare di dividere il tensore in microbatch. Per ulteriori informazioni, consulta smp.step nella documentazione API.

Ritardo dell'inizializzazione dei parametri

Per modelli molto grandi con oltre 100 miliardi di parametri, l'inizializzazione del peso tramite la memoria della CPU potrebbe causare un errore. out-of-memory Per ovviare a questo problema, la libreria offre un gestore di contesto smp.delay_param_initialization. Ciò ritarda l'allocazione fisica dei parametri fino a quando non passano alla GPU durante la prima esecuzione di una funzione smp.step decorata. Ciò evita un utilizzo non necessario della memoria della CPU durante l'inizializzazione dell'addestramento. Utilizza il gestore di contesto quando crei un oggetto modello, come mostrato nel codice seguente.

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

Tensor Parallelism per PyTorch

  • Se stai usando un seed per risultati deterministici, imposta il seed in base a smp.dp_rank() (ad esempio, torch.manual_seed(42 + smp.dp_rank())). Se non effettui questa operazione, diverse partizioni di un nn.Parameter vengono inizializzate nello stesso modo, con un impatto sulla convergenza.

  • SageMakerla libreria di parallelismo dei modelli utilizza NCCL per implementare i collettivi necessari per la distribuzione dei moduli. Soprattutto per i modelli più piccoli, se sulla GPU sono programmate troppe chiamate NCCL contemporaneamente, l'utilizzo della memoria potrebbe aumentare a causa dello spazio aggiuntivo utilizzato da NCCL. Per ovviare a questo problema, smp limita le chiamate NCCL in modo che il numero di operazioni NCCL in corso in un dato momento sia inferiore o uguale a un determinato limite. Il limite predefinito è 8, ma può essere regolato utilizzando la variabile di ambiente SMP_NCCL_THROTTLE_LIMIT. Se osservi un utilizzo della memoria maggiore del previsto durante l'utilizzo del parallelismo tensoriale, puoi provare a ridurre questo limite. Tuttavia, la scelta di un limite troppo piccolo potrebbe causare una perdita di throughput. Per disabilitare completamente la limitazione, puoi impostare SMP_NCCL_THROTTLE_LIMIT=-1.

  • La seguente identità, che vale quando il grado di parallelismo tensoriale è 1, non vale quando il grado di parallelismo tensoriale è maggiore di 1: smp.mp_size() * smp.dp_size() == smp.size(). Questo perché il gruppo parallelo tensoriale fa parte sia del gruppo di parallelismo del modello che del gruppo di parallelismo dei dati. Se il codice contiene riferimenti esistenti a mp_rank, mp_size, MP_GROUP e così via e se desideri lavorare solo con il gruppo pipeline parallel, potrebbe essere necessario sostituire i riferimenti con smp.pp_size(). Le seguenti identità sono sempre vere:

    • 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()

  • Poiché il wrapper smp.DistributedModel modifica i parametri del modello quando il parallelismo tensoriale è abilitato, l'ottimizzatore deve essere creato dopo la chiamata a smp.DistributedModel, con i parametri distribuiti. Ad esempio, quanto segue non funziona:

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

    Invece, l'ottimizzatore dovrebbe essere creato con i seguenti parametri del smp.DistributedModel:

    ## CORRECT model = smp.DistributedModel(MyModel()) optimizer = SomeOptimizer(model.optimizers())
  • Quando un modulo viene sostituito con la sua controparte distribuita tramite il parallelismo tensoriale, il modulo distribuito non eredita i suoi pesi dal modulo originale e inizializza nuovi pesi. Ciò significa che, ad esempio, se i pesi devono essere inizializzati in una particolare chiamata (ad esempio, tramite una chiamata load_state_dict), ciò deve avvenire dopo la chiamata smp.DistributedModel, una volta avvenuta la distribuzione del modulo.

  • Quando accedi direttamente ai parametri dei moduli distribuiti, tieni presente che il peso non ha la stessa forma del modulo originale. Ad esempio, 

    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)
  • L'uso di torch.utils.data.distributed.DistributedSampler è fortemente consigliato per il parallelismo tensoriale. Ciò garantisce che ogni classificazione di parallelismo dei dati riceva lo stesso numero di campioni di dati, il che impedisce i blocchi che potrebbero derivare dall'esecuzione di diversi numeri di fasi da parte di diversi dp_rank.

  • Se si utilizza la DistributedDataParallel classe join API of PyTorch per gestire i casi in cui ranghi paralleli di dati diversi hanno un numero diverso di batch, è comunque necessario assicurarsi che i ranghi che si trovano nello stesso TP_GROUP numero di batch; altrimenti i collettivi di comunicazione utilizzati nell'esecuzione distribuita dei moduli potrebbero bloccarsi. Le classificazioni che si trovano in TP_GROUP diversi possono avere un numero diverso di batch, purché venga utilizzata l'API join.

  • Se desideri controllare il tuo modello e utilizzare il parallelismo tensoriale, considera quanto segue:

    • Per evitare situazioni di stallo e condizioni di competizione durante il salvataggio e il caricamento dei modelli quando utilizzi il parallelismo tensoriale, assicurati di richiamare le funzioni appropriate dai seguenti stati del modello e dell'ottimizzatore all'interno di una classificazione di parallelismo dei dati ridotta.

    • Se stai effettuando la transizione di uno script parallelo di pipeline esistente e abiliti il parallelismo tensoriale per lo script, assicurati di modificare qualsiasi blocco if smp.dp_rank() == 0 utilizzato per il salvataggio e il caricamento con blocchi if smp.rdp_rank() == 0. In caso contrario, il processo di addestramento potrebbe bloccarsi.

    Per ulteriori informazioni sul checkpoint di un modello con parallelismo tensoriale, consulta Checkpoint di un modello distribuito.