Potenzia al massimo i tuoi sistemi di machine learning in 4 semplici passaggi

Migliora al massimo le tue abilità di machine learning in 4 semplici passaggi

Immagine generata con DALL.E-3

Benvenuti alla montagna russa dell’ottimizzazione dell’apprendimento automatico! In questo post vi mostrerò il mio processo per ottimizzare qualsiasi sistema di apprendimento automatico per un addestramento e un’infusione velocissimi in 4 semplici passaggi.

Immaginatevi questo: vi affidano finalmente un nuovo e interessante progetto di apprendimento automatico in cui dovete addestrare il vostro agente a contare quanti hot dog ci sono in una foto, il cui successo potrebbe far guadagnare alla vostra azienda decine di dollari!

Vi viene fornito il modello di rilevamento degli oggetti più all’avanguardia implementato nel vostro framework preferito che ha molti stelle su GitHub, eseguite alcuni esempi di prova e dopo un’ora o giù di lì, il modello riconosce hot dog come uno studente squattrinato al terzo anno di college: la vita è bella.

I passaggi successivi sono ovvi, vogliamo estendere il progetto ad affrontare problemi più difficili, ciò comporta l’utilizzo di più dati, un modello più grande e, ovviamente, un tempo di addestramento più lungo. Ora state passando da ore di addestramento a giorni. Questo va bene, però avete ignorato il resto del team per 3 settimane adesso e probabilmente dovreste dedicare una giornata a mettervi in pari con la revisione del codice e le e-mail passive-aggressive che si sono accumulate.

Tornate un giorno dopo avervi sentito soddisfatti dei commenti illuminanti e assolutamente necessary che avete lasciato sulle Merge Request dei vostri colleghi, solo per scoprire che le prestazioni sono crollate e si sono bloccate dopo un addestramento di 15 ore (la legge del contrappasso funziona velocemente).

Nei giorni successivi vi troverete in un vortice di prove, test ed esperimenti, in cui ogni idea potenziale richiede più di un giorno di esecuzione. Questi costano rapidamente centinaia di dollari in termini di calcolo, tutto per arrivare alla grande domanda: come possiamo rendere tutto più veloce e meno costoso?

Benvenuti alla montagna russa emozionale dell’ottimizzazione dell’apprendimento automatico! Ecco un semplice processo in 4 passi per ribaltare la situazione a vostro vantaggio:

  1. Benchmark
  2. Semplifica
  3. Ottimizza
  4. Ripeti

Questo è un processo iterativo e ci saranno molte volte in cui si ripeteranno alcuni passaggi prima di passare al successivo, quindi è più una cassetta degli attrezzi che un sistema a 4 passaggi, ma 4 passaggi suona meglio.

1 — Benchmark

“Misura due volte, taglia una volta” — Qualcuno saggio.

La prima (e probabilmente la seconda) cosa che dovreste sempre fare è profilare il vostro sistema. Questo può essere qualcosa di semplice come misurare il tempo che impiega per eseguire un determinato blocco di codice o qualcosa di complesso come fare una traccia completa del profilo. L’importante è avere abbastanza informazioni per identificare i colli di bottiglia del sistema. Io eseguo molti benchmark in base a dove ci troviamo nel processo e li divido in due tipi: benchmark di alto livello e benchmark di basso livello.

Alto Livello

Questo è il tipo di cose che mostrerete al vostro capo alla riunione settimanale “Quanto siamo nei guai?” e vorrete queste metriche come parte di ogni esecuzione. Queste vi daranno un senso generale delle prestazioni del vostro sistema.

Batch Per Secondo — quanto velocemente stiamo attraversando ciascuno dei nostri batch? dovrebbe essere il più alto possibile

Step Per Secondo — (specifico per RL) quanto velocemente stiamo avanzando nel nostro ambiente per generare i nostri dati, dovrebbe essere il più alto possibile. Ci sono alcune complicate interazioni tra il tempo di passo e i batch di addestramento di cui non parlerò qui.

Utilizzo GPU — quanto del vostro GPU viene utilizzato durante l’addestramento? Dovrebbe essere costantemente al massimo, se non lo è significa che c’è tempo inutilizzato che può essere ottimizzato.

Utilizzo CPU — quanto delle vostre CPU viene utilizzato durante l’addestramento? Anche questo dovrebbe essere il più vicino possibile al 100%.

FLOPS — operazioni in virgola mobile al secondo, questo vi dà un’idea di quanto efficacemente state utilizzando l’intero hardware a disposizione.

Basso Livello

Utilizzando le metriche sopra riportate potrete iniziare a indagare più a fondo per individuare i vostri colli di bottiglia. Una volta ottenute queste informazioni, vorrete iniziare a esaminare metriche più dettagliate e fare del profiling.

Profiling Temporale — Questo è l’esperimento più semplice e spesso più utile da eseguire. Strumenti di profiling come cprofiler possono essere utilizzati per avere una visione d’insieme dei tempi di esecuzione di ciascuno dei vostri componenti nel loro complesso o per osservare i tempi di esecuzione di componenti specifici.

Profilazione della memoria — Un altro strumento indispensabile per ottimizzare. I grandi sistemi richiedono molta memoria, quindi dobbiamo assicurarci di non sprecarne neanche un po’! Strumenti come memory-profiler ti aiuteranno a individuare dove il tuo sistema consuma la RAM.

Profilazione del modello — Strumenti come Tensorboard offrono eccellenti strumenti di profilazione per capire cosa sta influenzando le prestazioni del tuo modello.

Profilazione della rete — Il carico di rete è una causa comune di rallentamenti del sistema. Ci sono strumenti come wireshark per aiutarti a profilare questo aspetto, ma onestamente non lo uso mai. Preferisco fare una profilazione del tempo dei miei componenti e misurare il tempo totale che il componente richiede, per poi isolare quanto tempo viene impiegato dalle operazioni di input/output di rete.

Assicurati di leggere questo ottimo articolo sulla profilazione in Python di RealPython per ulteriori informazioni!

2 — Semplifica

Dopo aver identificato un’area che necessita di ottimizzazione tramite la profilazione, semplificala. Elimina tutto ciò che non riguarda quella parte. Riduci il sistema a parti più piccole fino a raggiungere il punto di rallentamento. Non aver paura di profilare durante la semplificazione, in questo modo assicuri di procedere nella giusta direzione durante l’iterazione. Ripeti questa procedura fino a trovare il punto di rallentamento.

Consigli

  • Sostituisci altri componenti con stub e funzioni mock che forniscono solo dati attesi.
  • Simula funzioni pesanti con funzioni di sleep o calcoli di prova.
  • Utilizza dati di prova per eliminare l’onere della generazione e dell’elaborazione dei dati.
  • Inizia con versioni locali e a processo singolo del tuo sistema prima di passare alla distribuzione.
  • Simula nodi multipli e attori su una singola macchina per eliminare il sovraccarico di rete.
  • Trova la prestazione massima teorica per ogni parte del sistema. Se tutti gli altri punti di rallentamento del sistema fossero risolti, eccetto per questo componente, qual è la prestazione prevista?
  • Profilare di nuovo! Ogni volta che semplifichi il sistema, ripeti la profilazione.

Domande

Una volta individuato il punto di rallentamento, ci sono alcune domande chiave alle quali vogliamo rispondere:

Qual è la prestazione massima teorica di questo componente?

Se abbiamo adeguatamente isolato il componente che crea il rallentamento, dovremmo essere in grado di rispondere a questa domanda.

Quanto siamo lontani dal massimo?

Questa differenza di prestazione ci informerà su quanto ottimizzato sia il nostro sistema. Potrebbe anche essere il caso che ci siano altri vincoli una volta che reintroduciamo il componente nel sistema, e questo va bene, ma è fondamentale essere consapevoli di quale sia questa differenza.

C’è un punto di rallentamento più profondo?

Fai sempre questa domanda, potrebbe essere il caso che il problema sia più profondo di quanto inizialmente pensato e, in tal caso, ripeti il processo di benchmarking e semplificazione.

3 — Ottimizza

Okay, quindi diciamo che abbiamo identificato il maggior punto di rallentamento, adesso viene la parte divertente, come possiamo migliorare le cose? Di solito ci sono 3 aree su cui dovremmo concentrarci per possibili miglioramenti

  1. Calcolo
  2. Comunicazione
  3. Memoria

Calcolo

Per ridurre i punti di rallentamento dovuti ai calcoli, dobbiamo essere il più efficienti possibile con i dati e gli algoritmi con cui lavoriamo. Questo è ovviamente specifico per ogni progetto e ci sono molte cose che possono essere fatte, ma vediamo alcune buone regole generali.

Parallelismo — fare il lavoro in parallelo il più possibile. Questo è il primo grande vantaggio nel progettare il sistema che può avere un impatto enorme sulle prestazioni. Guarda metodi come la vettorizzazione, il batching, il multi-threading e il multi-processing.

Caching — calcola in anticipo e riutilizza i calcoli dove possibile. Molti algoritmi possono beneficiare del riutilizzo di valori pre-calcolati e risparmiare risorse computazionali critiche per ciascun passo di addestramento.

Offloading – tutti sappiamo che Python non è noto per la sua velocità. Fortunatamente possiamo trasferire i calcoli critici a linguaggi di livello inferiore come C/C++.

Scaling dell’hardware – questa è una sorta di soluzione di compromesso, ma quando tutto il resto fallisce, possiamo sempre lanciare più computer al problema!

Comunicazione

Ogni ingegnere esperto ti dirà che la comunicazione è fondamentale per portare a termine un progetto di successo, e con ciò naturalmente intendiamo la comunicazione all’interno del nostro sistema (che Dio ci liberi dall’avere mai a che fare con i colleghi). Alcune buone regole da seguire sono:

Nessun tempo di inattività – Tutta l’hardware disponibile deve essere utilizzata in ogni momento, altrimenti si stanno perdendo miglioramenti delle prestazioni. Questo è solitamente a causa delle complicazioni e dell’onere della comunicazione tra il sistema.

Restare locali – Mantieni tutto su una singola macchina il più a lungo possibile prima di passare a un sistema distribuito. Ciò mantiene il sistema semplice e evita l’onere della comunicazione di un sistema distribuito.

Async > Sync – Identifica ciò che può essere fatto in modo asincrono, questo aiuterà a scaricare il costo della comunicazione mantenendo il lavoro in movimento mentre i dati vengono spostati.

Evitare lo spostamento dei dati – Spostare i dati dalla CPU alla GPU o da un processo ad un altro è costoso! Fai il meno possibile o riduci l’impatto svolgendolo in modo asincrono.

Memoria

Ultimo ma non meno importante è la memoria. Molte delle aree menzionate sopra possono essere utili per alleviare il tuo collo di bottiglia, ma potrebbe non essere possibile se non hai memoria disponibile! Esaminiamo alcune cose da considerare.

Tipi di dati – tienili il più piccoli possibile per ridurre il costo della comunicazione e della memoria e con acceleratori moderni, ridurrai anche il calcolo.

Cache – analogamente alla riduzione del calcolo, una cache intelligente può aiutarti a risparmiare memoria. Tuttavia, assicurati che la tua cache venga utilizzata abbastanza spesso da giustificarne l’uso.

Preallocazione – non è qualcosa a cui siamo abituati in Python, ma essere rigorosi con la preallocazione della memoria significa che sai esattamente di quanta memoria hai bisogno, riduci il rischio di frammentazione e se riesci a scrivere nella memoria condivisa, ridurrai la comunicazione tra i tuoi processi!

Garbage Collection – fortunatamente Python si occupa di gran parte di questo per noi, ma è importante assicurarsi di non mantenere valori grandi in ambito senza bisogno o peggio ancora, avere una dipendenza circolare che può causare una perdita di memoria.

Sii pigro – valuta le espressioni solo quando necessario. In Python, puoi utilizzare le espressioni generator invece delle comprehension list per operazioni che possono essere valutate in modo pigro.

4 – Ripeti

Allora, quando abbiamo finito? Beh, dipende davvero dal tuo progetto, dai requisiti e da quanto tempo ci vuole prima che la tua sanità mentale inizi a crollare!-

Man mano che rimuovi i colli di bottiglia, otterrai rendimenti decrescenti sul tempo e lo sforzo che stai mettendo nell’ottimizzare il tuo sistema. Durante il processo, devi decidere quando “buono” è abbastanza “buono”. Ricorda, la velocità è un mezzo per raggiungere una fine, non lasciarti intrappolare nell’ottimizzare per il gusto di farlo. Se non avrà un impatto sugli utenti, probabilmente è il momento di passare oltre.

Conclusioni

Costruire sistemi di ML su larga scala è DIFFICILE. È come giocare a un gioco contorto di “Dov’è Charlie” incrociato con Dark Souls. Se riesci a individuare il problema, devi fare più tentativi per risolverlo e passi la maggior parte del tempo a farti picchiare il sedere, chiedendoti “Perché sto facendo questo venerdì sera?”. Avere un approccio semplice e basato su principi può aiutarti a superare quella battaglia finale contro il capo e gustare quei dolci, dolci max teorici FLOPs.

ML in azione | Donal Byrne | Substack

La newsletter di apprendimento automatico che fornisce consigli non richiesti, insight pratici e lezioni apprese in modo rapido…

donalbyrne.substack.com