Accelerazione dei trasformatori PyTorch con Intel Sapphire Rapids – parte 2

'Accelerazione trasformatori PyTorch con Intel Sapphire Rapids - pt. 2'

In un recente post, ti abbiamo presentato la quarta generazione di CPU Intel Xeon, chiamata Sapphire Rapids, e il suo nuovo set di istruzioni Advanced Matrix Extensions (AMX). Combinando un cluster di server Sapphire Rapids che utilizzano Amazon EC2 e librerie Intel come l’Intel Extension for PyTorch, ti abbiamo mostrato come eseguire in modo efficiente il training distribuito su larga scala, ottenendo un aumento di velocità di 8 volte rispetto alla generazione precedente di Xeon (Ice Lake) con una scalabilità quasi lineare.

In questo post, ci concentreremo sull’inferenza. Lavorando con popolari modelli HuggingFace implementati con PyTorch, misureremo prima le loro prestazioni su un server Ice Lake per sequenze di token NLP corte e lunghe. Successivamente, faremo lo stesso con un server Sapphire Rapids e l’ultima versione di Hugging Face Optimum Intel, una libreria open-source dedicata all’accelerazione hardware per piattaforme Intel.

Iniziamo!

Perché dovresti considerare l’inferenza basata su CPU

Ci sono diversi fattori da considerare quando si decide se eseguire l’inferenza del deep learning su una CPU o su una GPU. Il più importante è sicuramente la dimensione del modello. In generale, i modelli più grandi possono trarre maggior beneficio dalla potenza di calcolo aggiuntiva fornita da una GPU, mentre i modelli più piccoli possono essere eseguiti in modo efficiente su una CPU.

Un altro fattore da considerare è il livello di parallelismo nel modello e nel compito di inferenza. Le GPU sono progettate per eccellere nel processing massivamente parallelo, quindi possono essere più efficienti per compiti che possono essere parallelizzati in modo efficace. D’altra parte, se il modello o il compito di inferenza non ha un livello molto elevato di parallelismo, una CPU può essere una scelta più efficace.

Il costo è anche un fattore importante da considerare. Le GPU possono essere costose e l’utilizzo di una CPU può essere un’opzione più conveniente, soprattutto se il caso d’uso aziendale non richiede latenza estremamente bassa. Inoltre, se hai bisogno della possibilità di aumentare o diminuire facilmente il numero di lavoratori di inferenza, o se hai bisogno di eseguire l’inferenza su una vasta varietà di hardware, l’utilizzo delle CPU può essere un’opzione più flessibile.

Ora, configuriamo i nostri server di test.

Configurazione dei nostri server di test

Come nel post precedente, utilizzeremo istanze Amazon EC2:

  • un’istanza c6i.16xlarge, basata sull’architettura Ice Lake,
  • un’istanza r7iz.16xlarge-metal, basata sull’architettura Sapphire Rapids. Puoi leggere di più sulla nuova famiglia r7iz sul sito web AWS.

Entrambe le istanze hanno 32 core fisici (quindi, 64 vCPU). Le configureremo allo stesso modo:

  • Ubuntu 22.04 con Linux 5.15.0 ( ami-0574da719dca65348 ),
  • PyTorch 1.13 con l’Intel Extension for PyTorch 1.13,
  • Transformers 4.25.1.

L’unica differenza sarà l’aggiunta della libreria Optimum Intel sulla istanza r7iz.

Ecco i passaggi di configurazione. Come al solito, consigliamo di utilizzare un ambiente virtuale per mantenere le cose ordinate.

sudo apt-get update

# Aggiungi libtcmalloc per prestazioni extra
sudo apt install libgoogle-perftools-dev -y
export LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libtcmalloc.so"

sudo apt-get install python3-pip -y
pip install pip --upgrade
export PATH=/home/ubuntu/.local/bin:$PATH
pip install virtualenv
virtualenv inference_env
source inference_env/bin/activate

pip3 install torch==1.13.0 -f https://download.pytorch.org/whl/cpu
pip3 install intel_extension_for_pytorch==1.13.0 -f https://developer.intel.com/ipex-whl-stable-cpu
pip3 install transformers

# Necessario solo sull'istanza r7iz
pip3 install optimum[intel]

Una volta completati questi passaggi sulle due istanze, possiamo iniziare ad eseguire i nostri test.

In questo esempio, effettueremo il benchmark di diversi modelli NLP su un compito di classificazione del testo: distilbert-base-uncased, bert-base-uncased e roberta-base. Puoi trovare lo script completo su Github. Sentiti libero di provarlo con i tuoi modelli!

modelli = ["distilbert-base-uncased", "bert-base-uncased", "roberta-base"]

Utilizzando frasi di 16 e 128 token, misureremo la latenza media e p99 per l’elaborazione singola e in batch. Questo ci darà una buona idea dell’accelerazione che possiamo aspettarci in scenari reali.

frase_corta = "Questo è un paio di scarpe veramente belle, sono completamente soddisfatto del mio acquisto"
array_frase_corta = [frase_corta] * 8

frase_lunga = "Queste scarpe Adidas Lite Racer colpiscono un bel punto di comfort. Nonostante siano un po' strette nella parte anteriore, sono molto comode da indossare e offrono un buon supporto. Non direi che sono delle buone scarpe da corsa o da allenamento perché mancano semplicemente del supporto alla caviglia e all'arco plantare che la maggior parte delle persone vorrebbe in quel tipo di scarpe e la suola si consuma piuttosto velocemente, ma sono sicuramente comode. In realtà ho camminato tutto il giorno a Disney World con queste scarpe senza problemi, se può essere un riferimento. In conclusione, uso queste scarpe per quello che sono meglio: versatili, economiche e comode, senza aspettarmi le prestazioni di una scarpa da ginnastica di alta qualità o il comfort del mio paio di pantofole preferite."
array_frase_lunga = [frase_lunga] * 8

La funzione di benchmarking è molto semplice. Dopo alcune iterazioni di riscaldamento, eseguiamo 1.000 previsioni utilizzando l’API di pipeline, memorizziamo i tempi di previsione e calcoliamo sia la loro media che il valore p99.

import time
import numpy as np

def benchmark(pipeline, dati, iterazioni=1000):
    # Riscaldamento
    for i in range(100):
        risultato = pipeline(dati)
    tempi = []
    for i in range(iterazioni):
        inizio = time.time()
        risultato = pipeline(dati)
        fine = time.time()
        tempi.append(fine - inizio)
    return "{:.2f}".format(np.mean(tempi) * 1000), "{:.2f}".format(
        np.percentile(tempi, 99) * 1000
    )

Sull’istanza c6i (Ice Lake), utilizziamo solo una pipeline di Transformers standard.

from transformers import pipeline

for modello in modelli:
    print(f"Benchmarking {modello}")
    pipe = pipeline("sentiment-analysis", model=modello)
    risultato = benchmark(pipe, frase_corta)
    print(f"Pipeline Transformers, frase breve: {risultato}")
    risultato = benchmark(pipe, frase_lunga)
    print(f"Pipeline Transformers, frase lunga: {risultato}")
    risultato = benchmark(pipe, array_frase_corta)
    print(f"Pipeline Transformers, array di frasi brevi: {risultato}")
    risultato = benchmark(pipe, array_frase_lunga)
    print(f"Pipeline Transformers, array di frasi lunghe: {risultato}")

Sull’istanza r7iz (Sapphire Rapids), utilizziamo sia una pipeline standard che una pipeline Optimum. Nella pipeline Optimum, abilitiamo la modalità bfloat16 per sfruttare le istruzioni AMX. Impostiamo anche jit su True per ottimizzare ulteriormente il modello con la compilazione just-in-time.

import torch
from optimum.intel import inference_mode

with inference_mode(pipe, dtype=torch.bfloat16, jit=True) as opt_pipe:
    risultato = benchmark(opt_pipe, frase_corta)
    print(f"Pipeline Optimum, frase breve: {risultato}")
    risultato = benchmark(opt_pipe, frase_lunga)
    print(f"Pipeline Optimum, frase lunga: {risultato}")
    risultato = benchmark(opt_pipe, array_frase_corta)
    print(f"Pipeline Optimum, array di frasi brevi: {risultato}")
    risultato = benchmark(opt_pipe, array_frase_lunga)
    print(f"Pipeline Optimum, array di frasi lunghe: {risultato}")

Per motivi di brevità, ci limiteremo ai risultati p99 per distilbert-base-uncased. Tutti i tempi sono espressi in millisecondi. Troverai i risultati completi alla fine del post.

Come puoi vedere nel grafico sopra, le previsioni singole vengono eseguite 60-65% più velocemente rispetto alla generazione precedente di CPU Xeon. In altre parole, grazie alla combinazione di Intel Sapphire Rapids e Hugging Face Optimum, puoi accelerare le tue previsioni del 3x con solo piccole modifiche al tuo codice.

Ciò ti permette di ottenere una latenza di previsione a cifra singola anche con sequenze di testo lunghe, cosa finora possibile solo con le GPU.

Conclusion

La quarta generazione dei processori Intel Xeon offre un’eccellente performance di inferenza, specialmente quando combinata con Hugging Face Optimum. Questo è un ulteriore passo verso la resa del Deep Learning più accessibile e conveniente, e non vediamo l’ora di continuare questo lavoro con i nostri amici di Intel.

Ecco alcune risorse aggiuntive per aiutarti a iniziare:

  • Intel IPEX su GitHub
  • Hugging Face Optimum su GitHub

Se hai domande o feedback, saremmo felici di leggerli sul forum di Hugging Face.

Grazie per la lettura!

Appendice: risultati completi

Ubuntu 22.04 con libtcmalloc, Linux 5.15.0 patchato per il supporto Intel AMX, PyTorch 1.13 con Estensione Intel per PyTorch, Transformers 4.25.1, Optimum 1.6.1, Optimum Intel 1.7.0.dev0