6

Parallelismo degli operatori e ottimizzazione della compilazione

AI999第4课:现代计算底座与张量化并行演化
00:00

Nel EvoX moderno substrato di calcolo, l'unità minima di esecuzione degli operatori evolutivi non è più il "individuo", ma l'interatensore di popolazione: operatori come mutazione, crossover e selezione coprono tutti gli individui con una sola chiamata; su questa base,la compilazione del grafo computazionaleorganizza la sequenza di operatori in un piano di esecuzione ottimizzabile, migliorando l'efficienza di esecuzione senza alterare la semantica dell'algoritmo.

Parallelismo degli operatori e ottimizzazione della compilazioneEvoluzione batch a livello di tensore · Ottimizzazione del piano di esecuzione tramite compilazione del grafo computazionaletensore di popolazione[N, D] · tutti gli individuiLa dimensione degli individui funge da dimensione batchUna sola chiamata copre tutti gli individuiUna chiamatacopre l'intera popolazioneGrafo computazionale degli operatori evolutiviMutazioneCrossoverSelezioneDipendenze tra operatori → grafo computazionaleOttimizzazione globale sull'intero grafoinvece di richiamare implementazioni pronte operatore per operatoreCompilazionePrima della compilazioneCiclo per singolo individuo · molteplici avvii di kernelMolti avvii di kernel, dimensione batch pari a 1Fusione di operatori · Generazione di kernel · Riutilizzo della memoria · Ottimizzazione della schedulazioneDopo la compilazioneGrafo computazionale fuso · pochi kernel efficientiUn singolo forward batch completa l'intera generazione evolutivaCome calcolare · Modalità di esecuzioneNumero di kernelTensore intermedioStrategia di memoria e schedulazioneL'ottimizzazione agisce solo a livello esecutivo, senza modificare le regole di aggiornamento degli operatoriCosa calcolare · Semantica dell'algoritmoMutazioneCrossoverSelezioneCon lo stesso seed casuale, i risultati evolutivi rimangono coerenti≠

Parallelismo degli operatori: evoluzione batch a livello di tensore

  • Operatori a livello di tensore: mutazione, crossover e selezione possono agire direttamente su un tensore di popolazione di forma [N, D] (dove N è la dimensione della popolazione e D la dimensionalità delle variabili decisionali), e una singola chiamata dell'operatore copre tutti gli N individui.
  • Eliminazione del ciclo per singolo individuo: non è più necessario scrivere for ind in population: ind = mutate(ind) questo tipo di ciclo seriale in Python/CPU, riducendo così l'overhead dovuto alle frequenti chiamate di funzione e alle invocazioni del framework.
  • Scambiare la dimensione batch con il throughput: una volta che la dimensione degli individui funge da dimensione batch, gli operatori vengono eseguiti sul dispositivo in modo massivamente parallelo, e il throughput dell'evoluzione batch cresce con la dimensione della popolazione.

Compilazione del grafo computazionale: dalla sequenza di operatori al piano di esecuzione

Organizzando le dipendenze tra operatori "mutazione → crossover → selezione" in ungrafo computazionale, il compilatore può eseguire un'ottimizzazione globale sull'intero grafo, invece di limitarsi a richiamare implementazioni pronte operatore per operatore:

  • Fusione di operatori: combina più operazioni element-wise in un unico kernel, riducendo la creazione di tensori intermedi e le letture/scritture ripetute.
  • Generazione e schedulazione dei kernel: genera kernel efficienti per il sottografo fuso, completando l'intera generazione evolutiva con un numero minore di avvii di kernel.
  • Riutilizzo della memoria: pianifica in anticipo i buffer per i risultati intermedi, riducendo l'overhead aggiuntivo dovuto all'allocazione della memoria e al trasferimento dei dati.

L'efficienza cambia, la semantica no

Occorre distinguereil "come calcolare"dail "cosa calcolare". Il parallelismo degli operatori e l'ottimizzazione della compilazione modificano solo le modalità di esecuzione — numero di kernel, dimensione dei tensori intermedi, strategia di memoria e schedulazione — senza alterare il significato logico di mutazione, crossover e selezione. Il livello algoritmico continua a combinare gli operatori secondo la semantica evolutiva, quindi con lo stesso seed casuale la traiettoria evolutiva e i risultati prima e dopo l'ottimizzazione dovrebbero coincidere. In una frase: la tensorizzazione risolve la questione "quanto velocemente si calcola in batch", la compilazione del grafo computazionale risolve "quanto è ottimale la schedulazione dei kernel", mentre "cosa si calcola" rimane invariato.

Errori comuni
Scambiare la "tensorizzazione" per "un cambio di algoritmo evolutivo". La tensorizzazione si limita ad applicare lo stesso operatore all'intero tensore di popolazione, e le regole di aggiornamento tra individui non cambiano; anche l'ottimizzazione della compilazione non rende l'algoritmo "più intelligente". Ciò di cui diffidare davvero è non eseguire una verifica di regressione dopo l'ottimizzazione, scambiando le differenze di implementazione per miglioramenti algoritmici.