Tutorial pratico su Python
Esistono due passaggi per distribuire un modello utilizzando TensorRT-LLM:
- Compilare il modello
- 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
- Clona il Repository git TensorRT-LLM. Questo repository contiene tutti i moduli e gli script necessari per compilare il modello.
!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 userdatasnapshot_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 HfApifor 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.
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:
- 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 conmodel/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 comeprompt
,max_new_tokens
,temperature
ecc. Estraiamo tutti questi valori nella parte superiore della funzione utilizzando il filerequest.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 Pathtr = 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 requestsdata = {"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:
Richiesta media:
Richiesta ampia:
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