Distribuzione di LLM in produzione utilizzando TensorRT LLM |  di Het Trivedi |  Febbraio 2024

 | Intelligenza-Artificiale

Tutorial pratico su Python

Esistono due passaggi per distribuire un modello utilizzando TensorRT-LLM:

  1. Compilare il modello
  2. Distribuire il modello compilato come endpoint API REST

Passaggio 1: compilazione del modello

Per questo tutorial lavoreremo con Mistral 7B istruire v0.2. Come accennato in precedenza, la fase di compilazione richiede una GPU. Ho scoperto che il modo più semplice per compilare un modello è su un notebook Google Colab.

TensorRT LLM è supportato principalmente su GPU Nvidia di fascia alta. Ho eseguito Google Colab su una GPU A100 da 40 GB e utilizzerò la stessa GPU anche per la distribuzione.

!git clone https://github.com/NVIDIA/TensorRT-LLM.git
%cd TensorRT-LLM/examples/llama
!pip install tensorrt_llm -U --pre --extra-index-url https://pypi.nvidia.com
!pip install huggingface_hub pynvml mpi4py
!pip install -r requirements.txt
  • Installa le dipendenze Python necessarie.
from huggingface_hub import snapshot_download
from google.colab import userdata

snapshot_download(
"mistralai/Mistral-7B-Instruct-v0.2",
local_dir="tmp/hf_models/mistral-7b-instruct-v0.2",
max_workers=4
)

  • Scarica i pesi del modello Mistral 7B Instruct v0.2 da Hugging Face e memorizzali in una directory locale all’indirizzo tmp/hf_models/mistral-7b-instruct-v0.2
  • Se guardi dentro il tmp/hf_models directory in Colab dovresti vedere i pesi del modello lì.
!python convert_checkpoint.py --model_dir ./tmp/hf_models/mistral-7b-instruct-v0.2 \
--output_dir ./tmp/trt_engines/1-gpu/ \
--dtype float16
  • Non è possibile compilare i pesi del modello grezzo. Devono invece essere convertiti in uno specifico formato LLM tensorRT.
  • IL convert_checkpoint.py Lo script prende i pesi Mistral grezzi e li converte in un formato compatibile.
  • IL --model_dir è il percorso per i pesi del modello grezzo.
  • IL --output_dir è il percorso dei pesi convertiti.
!trtllm-build --checkpoint_dir ./tmp/trt_engines/1-gpu/ \
--output_dir ./tmp/trt_engines/compiled-model/ \
--gpt_attention_plugin float16 \
--gemm_plugin float16 \
--max_input_len 32256
  • IL trtllm-build il comando compila il modello. In questa fase puoi anche passare vari flag di ottimizzazione. Per mantenere le cose semplici, non ho utilizzato alcuna ottimizzazione aggiuntiva.
  • IL --checkpoint_dir è il percorso verso il convertito pesi del modello.
  • IL --output_dir è dove il modello compilato viene salvato.
  • Mistral 7B Instruct v0.2 supporta una lunghezza del contesto di 32K. Ho impostato la lunghezza del contesto utilizzando il file--max_input_length bandiera.

Nota: la compilazione del modello può richiedere 15-30 minuti

Una volta compilato il modello, puoi caricare il modello compilato su mozzo del viso abbracciato. Per caricare i file su Hugging Face Hub avrai bisogno di un token di accesso valido che abbia SCRIVERE accesso.

import os
from huggingface_hub import HfApi

for root, dirs, files in os.walk(f"tmp/trt_engines/compiled-model", topdown=False):
for name in files:
filepath = os.path.join(root, name)
filename = "/".join(filepath.split("/")(-2:))
print("uploading file: ", filename)
api = HfApi(token=userdata.get('HF_WRITE_TOKEN'))
api.upload_file(
path_or_fileobj=filepath,
path_in_repo=filename,
repo_id="<your-repo-id>/mistral-7b-v0.2-trtllm"
)

  • Questo codice carica il modello compilato, il .motore file, per abbracciare il viso sotto il tuo ID utente.
  • Sostituisci il nel codice con il repository del tuo volto abbracciato che di solito è l’ID utente del tuo volto abbracciato.

Eccezionale! Questo conclude la parte di compilazione del modello. Sulla fase di distribuzione.

Passaggio 2: distribuzione del modello compilato

Esistono molti modi per distribuire questo modello compilato. Puoi usare uno strumento semplice come API veloce o qualcosa di più complesso come il server di inferenza triton.

Quando si utilizza uno strumento come FastAPI, lo sviluppatore deve impostare il server API, scrivere il Dockerfile e configurare correttamente CUDA. Gestire queste cose può essere una vera seccatura e rovina l’esperienza complessiva dello sviluppatore.

Per evitare questi problemi, ho deciso di utilizzare un semplice strumento open source chiamato Travatura. Truss consente agli sviluppatori di confezionare facilmente i propri modelli con il supporto GPU ed eseguirli su qualsiasi ambiente cloud. Ha tantissime fantastiche funzionalità che rendono i modelli di containerizzazione un gioco da ragazzi:

  • Supporto GPU pronto all’uso. Non c’è bisogno di avere a che fare con CUDA.
  • Creazione automatica di Dockerfile. Non c’è bisogno di scriverlo tu stesso.
  • Server API pronto per la produzione
  • Semplice interfaccia Python

Il vantaggio principale dell’utilizzo di Truss è che puoi facilmente containerizzare un modello con supporto GPU e distribuirlo in qualsiasi ambiente cloud.

Costruisci la capriata una volta. La distribuzione è ovunque.

Creazione della capriata

Crea o apri un ambiente virtuale Python con versione Python ≥ 3.8 e installa la seguente dipendenza:

pip install --upgrade truss

(Opzionale) Se vuoi creare il tuo progetto Truss da zero puoi eseguire il comando:

truss init mistral-7b-tensort-llm

Ti verrà richiesto di dare un nome al tuo modello. Qualsiasi nome come Mistral 7B Tensort LLM andrà bene. L’esecuzione del comando precedente auto genera i file richiesti per distribuire una capriata.

Per accelerare un po’ il processo, ho un repository Github che contiene i file richiesti. Si prega di clonare il repository Github di seguito:

Questo è l’aspetto che dovrebbe avere la struttura delle directory mistral-7b-tensorrt-llm-truss :

├── mistral-7b-tensorrt-llm-truss
│ ├── config.yaml
│ ├── model
│ │ ├── __init__.py
│ │ └── model.py
| | └── utils.py
| ├── requirements.txt

Ecco una rapida ripartizione dello scopo per cui vengono utilizzati i file sopra:

  1. IL config.yaml viene utilizzato per impostare varie configurazioni per il modello, incluse le sue risorse, dipendenze, variabili ambientali e altro. Qui è dove possiamo specificare il nome del modello, quali dipendenze Python installare e quali pacchetti di sistema installare.

2. Il model/model.py è il cuore di Truss. Contiene il codice Python che verrà eseguito sul server Truss. Nel model.py ci sono due metodi principali: load() E predict() .

  • IL load Il metodo è dove scaricheremo il modello compilato da Hugging Face e inizializzeremo il motore TensorRT LLM.
  • IL predict il metodo riceve richieste HTTP e chiama il modello.

3. Il model/utils.py contiene alcune funzioni di supporto per model.py file. Non ho scritto il utils.py file me stesso, l’ho preso direttamente dal Repository LLM TensorRT.

4. Il requirements.txt contiene le dipendenze Python necessarie per eseguire il nostro modello compilato.

Spiegazione più approfondita del codice:

IL model.py contiene il codice principale che viene eseguito, quindi scaviamo un po’ più a fondo in quel file. Diamo prima un’occhiata a load funzione.

import subprocess
subprocess.run(("pip", "install", "tensorrt_llm", "-U", "--pre", "--extra-index-url", "https://pypi.nvidia.com"))

import torch
from model.utils import (DEFAULT_HF_MODEL_DIRS, DEFAULT_PROMPT_TEMPLATES,
load_tokenizer, read_model_name, throttle_generator)

import tensorrt_llm
import tensorrt_llm.profiler
from tensorrt_llm.runtime import ModelRunnerCpp, ModelRunner
from huggingface_hub import snapshot_download

STOP_WORDS_LIST = None
BAD_WORDS_LIST = None
PROMPT_TEMPLATE = None

class Model:
def __init__(self, **kwargs):
self.model = None
self.tokenizer = None
self.pad_id = None
self.end_id = None
self.runtime_rank = None
self._data_dir = kwargs("data_dir")

def load(self):
snapshot_download(
"htrivedi99/mistral-7b-v0.2-trtllm",
local_dir=self._data_dir,
max_workers=4,
)

self.runtime_rank = tensorrt_llm.mpi_rank()

model_name, model_version = read_model_name(f"{self._data_dir}/compiled-model")
tokenizer_dir = "mistralai/Mistral-7B-Instruct-v0.2"

self.tokenizer, self.pad_id, self.end_id = load_tokenizer(
tokenizer_dir=tokenizer_dir,
vocab_file=None,
model_name=model_name,
model_version=model_version,
tokenizer_type="llama",
)

runner_cls = ModelRunner
runner_kwargs = dict(engine_dir=f"{self._data_dir}/compiled-model",
lora_dir=None,
rank=self.runtime_rank,
debug_mode=False,
lora_ckpt_source="hf",
)

self.model = runner_cls.from_dir(**runner_kwargs)

Cosa sta succedendo qui:

  • Nella parte superiore del file importiamo i moduli necessari, nello specifico tensorrt_llm
  • Successivamente, all’interno della funzione di caricamento, scarichiamo il modello compilato utilizzando il file snapshot_download funzione. Il mio modello compilato si trova al seguente ID repository: htrivedi99/mistral-7b-v0.2-trtllm . Se hai caricato il modello compilato altrove, aggiorna questo valore di conseguenza.
  • Quindi, scarichiamo il tokenizzatore per il modello utilizzando il file load_tokenizer funzione fornita con model/utils.py .
  • Infine, utilizziamo TensorRT LLM per caricare il nostro modello compilato utilizzando il file ModelRunner classe.

Ottimo, diamo un’occhiata a predict anche funzionare.

def predict(self, request: dict):

prompt = request.pop("prompt")
max_new_tokens = request.pop("max_new_tokens", 2048)
temperature = request.pop("temperature", 0.9)
top_k = request.pop("top_k",1)
top_p = request.pop("top_p", 0)
streaming = request.pop("streaming", False)
streaming_interval = request.pop("streaming_interval", 3)

batch_input_ids = self.parse_input(tokenizer=self.tokenizer,
input_text=(prompt),
prompt_template=None,
input_file=None,
add_special_tokens=None,
max_input_length=1028,
pad_id=self.pad_id,
)
input_lengths = (x.size(0) for x in batch_input_ids)

outputs = self.model.generate(
batch_input_ids,
max_new_tokens=max_new_tokens,
max_attention_window_size=None,
sink_token_length=None,
end_id=self.end_id,
pad_id=self.pad_id,
temperature=temperature,
top_k=top_k,
top_p=top_p,
num_beams=1,
length_penalty=1,
repetition_penalty=1,
presence_penalty=0,
frequency_penalty=0,
stop_words_list=STOP_WORDS_LIST,
bad_words_list=BAD_WORDS_LIST,
lora_uids=None,
streaming=streaming,
output_sequence_lengths=True,
return_dict=True)

if streaming:
streamer = throttle_generator(outputs, streaming_interval)

def generator():
total_output = ""
for curr_outputs in streamer:
if self.runtime_rank == 0:
output_ids = curr_outputs('output_ids')
sequence_lengths = curr_outputs('sequence_lengths')
batch_size, num_beams, _ = output_ids.size()
for batch_idx in range(batch_size):
for beam in range(num_beams):
output_begin = input_lengths(batch_idx)
output_end = sequence_lengths(batch_idx)(beam)
outputs = output_ids(batch_idx)(beam)(
output_begin:output_end).tolist()
output_text = self.tokenizer.decode(outputs)

current_length = len(total_output)
total_output = output_text
yield total_output(current_length:)
return generator()
else:
if self.runtime_rank == 0:
output_ids = outputs('output_ids')
sequence_lengths = outputs('sequence_lengths')
batch_size, num_beams, _ = output_ids.size()
for batch_idx in range(batch_size):
for beam in range(num_beams):
output_begin = input_lengths(batch_idx)
output_end = sequence_lengths(batch_idx)(beam)
outputs = output_ids(batch_idx)(beam)(
output_begin:output_end).tolist()
output_text = self.tokenizer.decode(outputs)
return {"output": output_text}

Cosa sta succedendo qui:

  • IL predict la funzione accetta alcuni input del modello come prompt , max_new_tokens , temperature ecc. Estraiamo tutti questi valori nella parte superiore della funzione utilizzando il file request.pop metodo.
  • Successivamente, formattiamo il prompt nel formato richiesto per TensorRT LLM utilizzando il file self.parse_input funzione di aiuto.
  • Quindi, chiamiamo il nostro modello LLM per generare gli output utilizzando il file self.model.generate funzione. La funzione generate accetta una varietà di argomenti che aiutano a controllare l’output di LLM.
  • Ho anche aggiunto del codice per abilitare lo streaming producendo un file generator oggetto. Se lo streaming è disabilitato, il tokenizzatore decodifica semplicemente l’output di LLM e lo restituisce come oggetto JSON.

Eccezionale! Questo copre la parte di codifica. Containerizziamolo.

Containerizzare il modello:

Per eseguire il nostro modello nel cloud dobbiamo containerizzarlo. Truss si occuperà di creare il Dockerfile e di impacchettare tutto per noi, quindi non dobbiamo fare molto.

Al di fuori del mistral-7b-tensorrt-llm-truss directory crea un file chiamato main.py . Incolla al suo interno il seguente codice:

import truss
from pathlib import Path

tr = truss.load("./mistral-7b-tensorrt-llm-truss")
command = tr.docker_build_setup(build_dir=Path("./mistral-7b-tensorrt-llm-truss"))
print(command)

Corri il main.py file e guarda all’interno del file mistral-7b-tensorrt-llm-truss directory. Dovresti vedere un gruppo di file generati automaticamente. Non dobbiamo preoccuparci del significato di questi file, è solo Truss che fa la sua magia.

Successivamente, creiamo il nostro contenitore utilizzando la finestra mobile. Esegui i comandi seguenti in sequenza:

docker build mistral-7b-tensorrt-llm-truss -t mistral-7b-tensorrt-llm-truss:latest
docker tag mistral-7b-tensorrt-llm-truss <docker_user_id>/mistral-7b-tensorrt-llm-truss
docker push <docker_user_id>/mistral-7b-tensorrt-llm-truss

Dolce! Siamo pronti per distribuire il modello nel cloud!

Distribuzione del modello in GKE

Per questa sezione, distribuiremo il modello su Google Kubernetes Engine. Se ricordi, durante la fase di compilazione del modello abbiamo eseguito Google Colab su una GPU A100 da 40 GB. Affinché TensorRT LLM funzioni, dobbiamo distribuire il modello esattamente sulla stessa GPU per l’inferenza.

Non approfondirò come configurare un cluster GKE poiché non rientra nell’ambito di questo articolo. Ma fornirò le specifiche che ho usato per il cluster. Ecco le specifiche:

  • 1 nodo, cluster Kubernetes standard (non pilota automatico)
  • Versione 1.28.5 gke kubernetes
  • 1 GPU Nvidia A100 da 40 GB
  • macchina a2-highgpu-1g (12 vCPU, 85 GB di memoria)
  • Installazione del driver GPU gestito da Google (altrimenti dobbiamo installare manualmente il driver Cuda)
  • Tutto questo verrà eseguito su un’istanza spot

Una volta configurato il cluster, possiamo avviarlo e connetterci ad esso. Dopo che il cluster è attivo e ti sei connesso correttamente ad esso, crea la seguente distribuzione Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
name: mistral-7b-v2-trt
namespace: default
spec:
replicas: 1
selector:
matchLabels:
component: mistral-7b-v2-trt-layer
template:
metadata:
labels:
component: mistral-7b-v2-trt-layer
spec:
containers:
- name: mistral-container
image: htrivedi05/mistral-7b-v0.2-trt:latest
ports:
- containerPort: 8080
resources:
limits:
nvidia.com/gpu: 1
nodeSelector:
cloud.google.com/gke-accelerator: nvidia-tesla-a100
---
apiVersion: v1
kind: Service
metadata:
name: mistral-7b-v2-trt-service
namespace: default
spec:
type: ClusterIP
selector:
component: mistral-7b-v2-trt-layer
ports:
- port: 8080
protocol: TCP
targetPort: 8080

Questa è una distribuzione Kubernetes standard che esegue un contenitore con l’immagine htrivedi05/mistral-7b-v0.2-trt:latest . Se hai creato la tua immagine nella sezione precedente, vai avanti e usala. Sentiti libero di usare il mio altrimenti.

È possibile creare la distribuzione eseguendo il comando:

kubectl create -f mistral-deployment.yaml

Sono necessari alcuni minuti per il provisioning del pod Kubernetes. Una volta che il pod è in esecuzione, verrà eseguita la funzione di caricamento che abbiamo scritto in precedenza. Puoi controllare i log del pod eseguendo il comando:

kubectl logs <pod-name>

Una volta caricato il modello, vedrai qualcosa di simile Completed model.load() execution in 449234 ms nei log del pod. Per inviare una richiesta al modello tramite HTTP è necessario eseguire il port forwarding del servizio. Puoi usare il comando seguente per farlo:

kubectl port-forward svc/mistral-7b-v2-trt-service 8080

Grande! Possiamo finalmente iniziare a inviare richieste alla modella! Apri qualsiasi script Python ed esegui il seguente codice:

import requests

data = {"prompt": "What is a mistral?"}
res = requests.post("http://127.0.0.1:8080/v1/models/model:predict", json=data)
res = res.json()
print(res)

Vedrai un output come il seguente:

{"output": "A Mistral is a strong, cold wind that originates in the Rhone Valley in France. It is named after the Mistral wind system, which is associated with the northern Mediterranean region. The Mistral is known for its consistency and strength, often blowing steadily for days at a time. It can reach speeds of up to 130 kilometers per hour (80 miles per hour), making it one of the strongest winds in Europe. The Mistral is also known for its clear, dry air and its role in shaping the landscape and climate of the Rhone Valley."}

Le prestazioni di TensorRT LLM possono essere notate visibilmente quando i token vengono trasmessi in streaming. Ecco un esempio di come farlo:

data = {"prompt": "What is mistral wind?", "streaming": True, "streaming_interval": 3}
res = requests.post("http://127.0.0.1:8080/v1/models/model:predict", json=data, stream=True)

for content in res.iter_content():
print(content.decode("utf-8"), end="", flush=True)

Questo modello maestrale ha una finestra di contesto abbastanza ampia, quindi sentiti libero di provarlo con suggerimenti diversi.

Benchmark delle prestazioni

Semplicemente osservando i token trasmessi in streaming, puoi probabilmente capire che TensorRT LLM è davvero veloce. Tuttavia, volevo ottenere numeri reali per acquisire i miglioramenti in termini di prestazioni derivanti dall’utilizzo di TensorRT LLM. Ho eseguito alcuni benchmark personalizzati e ho ottenuto i seguenti risultati:

Piccolo suggerimento:

Benchmark Hugging Face e TensorRT LLM per piccoli suggerimenti

Richiesta media:

Benchmark Hugging Face e TensorRT LLM per prompt medio

Richiesta ampia:

Benchmark Hugging Face e TensorRT LLM per prompt di grandi dimensioni

Conclusione

In questo post del blog, il mio obiettivo era dimostrare come è possibile ottenere un’inferenza all’avanguardia utilizzando TensorRT LLM. Abbiamo coperto tutto, dalla compilazione di un LLM alla distribuzione del modello in produzione.

Sebbene TensorRT LLM sia più complesso di altri ottimizzatori di inferenza, le prestazioni parlano da sole. Questo strumento fornisce ottimizzazioni LLM all’avanguardia pur essendo completamente open source ed è progettato per uso commerciale. Questo quadro è ancora nelle fasi iniziali ed è in fase di sviluppo attivo. Le prestazioni che vediamo oggi non potranno che migliorare nei prossimi anni.

Spero che tu abbia trovato qualcosa di prezioso in questo articolo. Grazie per aver letto!

Ti è piaciuta questa storia?

Prendere in considerazione abbonamento gratuito.

immagini

Se non diversamente specificato, tutte le immagini sono create dall’autore.

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *