Ricerca motori di inferenza potenziati dal recupero (RAG) con LangChain su CPU

Ricerca avanzata con motori di inferenza potenziati da recupero (RAG) utilizzando LangChain su CPU

Creato con Nightcafe - Proprietà dell'Autore

Esplorare scala, fedeltà e latenza nelle applicazioni di intelligenza artificiale con RAG

Anche se la Retrieval Augmented Generation (RAG) è ampiamente coperta, in particolare nella sua applicazione ai LLM basati su chat, in questo articolo ci proponiamo di guardarla da una prospettiva diversa e analizzare la sua potenza come potente strumento operativo. Forniremo anche un utile esempio pratico per acquisire esperienza pratica con le applicazioni basate su RAG. Alla fine dell’articolo, svilupperai un punto di vista unico su RAG, comprendendo il suo ruolo e il suo potenziale nelle implementazioni su larga scala di LLM in produzione.

Ma prima, rinfreschiamo la nostra comprensione dell’inferenza

L’inferenza è il processo che trasforma i dati in previsioni. Questo componente del ciclo di vita dell’Apprendimento Automatico spesso è supportato da pipeline di dati che gestiscono attività di pre-elaborazione e post-elaborazione. Valutiamo un esempio pratico: considera il sistema di raccomandazione di un servizio di streaming musicale, come mostrato nella Figura 1. Quando un utente visita la piattaforma di streaming, gli viene presentata un’intelligente lista selezionata di 10 canzoni nella sua interfaccia. Il sistema di raccomandazione responsabile di questa lista si basa su un modello addestrato e su pipeline di dati robuste per garantire un risultato di alta qualità.

Figura 1. Diagramma semplice che illustra un sistema di raccomandazione che supporta la funzionalità delle

La fase di pre-elaborazione, rappresentata dalle caselle gialle nel nostro diagramma, è cruciale per garantire che la previsione del modello sia in linea con i gusti unici dell’utente. A partire dalle ultime 250 canzoni riprodotte dall’utente, la pipeline elabora i dati e genera un insieme di caratteristiche di contesto prima di passarli a un modello addestrato per l’inferenza. Lo step dell’inferenza prevede cosa potrebbe piacere a questo utente e produce un risultato che passa a una fase di post-elaborazione (illustrata in arancione). In quest’ultimo passaggio, le prime raccomandazioni del modello vengono arricchite con metadati aggiuntivi: copertine degli album, titoli delle canzoni, nomi degli artisti e la loro classifica. Queste informazioni vengono quindi visualizzate sull’interfaccia dell’utente per la fruizione.

Nel flusso di lavoro descritto sopra, è chiaro quanto sia prossimo lo step dell’inferenza per l’utente nella topologia dell’applicazione. A differenza di altri componenti del ciclo di vita dell’IA come la raccolta dei dati e l’ottimizzazione del modello, che tendono a operare in background, il motore di inferenza si trova in prima linea, interagendo strettamente con l’interfaccia utente (UI) e l’esperienza utente (UX).

Figura 2. Un'illustrazione dei vari componenti di un sistema di IA e della loro prossimità all'esperienza utente. Immagine dell'autore

Possiamo utilizzare il diagramma sopra (Figura 2) per illustrare la prossimità dei vari componenti del ciclo di vita dell’IA all’utente. Mentre molti componenti come la raccolta dei dati e l’annotazione si trovano “dietro le quinte”, il motore di inferenza rappresenta un ponte critico tra i processi interni dell’IA e ciò a cui gli utenti finali sono esposti. Non è solo un altro meccanismo di back-end, ma è una parte fondamentale dell’esperienza tangibile dell’utente con l’applicazione.

Dato questo ruolo critico nella modellazione dell’esperienza utente, è essenziale che il motore di inferenza – che comprende il processo di inferenza e i suoi componenti periferici come la pre/elaborazione, la gestione delle API e la gestione del calcolo – funzioni senza problemi. Per stabilire delle condizioni limite per la qualità del motore di inferenza, introduco il “Triangolo della Qualità dell’Inferenza (IQ)” rappresentato nella Figura 3. Questa figura qualitativa mette in evidenza tre aspetti fondamentali su cui concentrarsi per migliorare le prestazioni di un carico di lavoro di inferenza:

  • Latency: se il motore di inferenza impiega meno tempo per fornire una risposta, si riduce l’overhead per l’applicazione e si ottiene una migliore esperienza utente.
  • Fedeltà: l’inferenza deve fornire risposte che gli utenti possono fidarsi e in cui sentirsi sicuri. Ciò include, ma non si limita, ad assicurare un’alta accuratezza delle risposte e ridurre le false rappresentazioni.
  • Scalabilità: dato che il carico sul sistema IA è variabile, la capacità di scalare l’infrastruttura è cruciale per ottimizzare i costi e consentire la corretta dimensionamento delle risorse di calcolo.
Figura 3. Un modello semplicistico per allineare tre elementi chiave di motori di inferenza di alta qualità. Un forte focus sull'eccellenza operativa, in cui i sistemi di intelligenza artificiale devono scalare mantenendo bassa latenza e restituendo risultati ad alta fedeltà. - Immagine dell'autore

Man mano che avanziamo nell’articolo, faremo riferimento al Triangle IQ per approfondire come questi tre componenti – latenza, fedeltà e scala – si allineino bene con il carico di lavoro RAG.

Breve Introduzione alla Generazione con Augmentazione di Recupero

La generazione con augmentazione di recupero, anche nota come RAG, è una tecnica introdotta inizialmente da Piktus et al. (2021) in “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks” ed è stata successivamente adattata in vari framework e applicazioni. RAG rientra nella categoria delle tecniche di apprendimento in contesto, che si concentrano nel fornire conoscenza aggiuntiva ai modelli pre-addestrati nell’obiettivo di migliorare la qualità delle loro risposte.

La caratteristica distintiva di RAG sta nel recupero intelligente di informazioni aggiuntive da fonti di dati rilevanti, tipicamente database vettoriali utilizzando algoritmi come la ricerca di similarità. I dati recuperati vengono combinati con la query dell’utente, arricchendo l’input fornito al modello generativo. Un flusso di lavoro RAG standard è rappresentato nella Figura 4.

Figura 4. Un diagramma di flusso di lavoro RAG semplice - Immagine dell'autore

Per comprendere il vero valore di RAG, consideriamo uno scenario pratico: un analista finanziario presso una grande impresa (Figura 5) è alle prese con l’attività di compilare un rapporto trimestrale sui guadagni. Eseguire questa attività in modo tradizionale richiederebbe molto tempo. Le applicazioni basate su modelli LLM offrono significativi miglioramenti in termini di efficienza, ma c’è un problema: la necessità di informazioni proprietarie e aggiornate, che non sono accessibili al momento dell’addestramento dei modelli open-source fondamentali. Questo potrebbe essere parzialmente risolto tramite il fine-tuning, ma il ritmo veloce delle operazioni aziendali farebbe sì che questo processo richieda un continuo fine-tuning per mantenere i modelli aggiornati.

Figura 5. Versione adattata della Figura 6. In questa versione, viene rappresentato uno scenario che coinvolge l'uso di RAG da parte di un analista finanziario per illustrare la validità del flusso di lavoro nella costruzione di sistemi RAG basati su dati proprietari. - Immagine dell'autore

RAG affronta queste sfide recuperando dati rilevanti e in tempo reale, consentendo ai modelli di essere aggiornati dinamicamente con le ultime informazioni. Ciò mette pressione sulla qualità del database sottostante, ma almeno la gestione dei dati è più empirica e prevedibile rispetto alle allucinazioni LLM e all’estrapolazione delle reti neurali. Come ulteriore vantaggio, questo approccio protegge i dati sensibili nell’infrastruttura dati dell’organizzazione.

Molti ingegneri di intelligenza artificiale applicata concordano sul fatto che dovrebbe esserci un cambiamento verso una strategia ibrida incentrata sul fine-tuning periodico e su robusti flussi di lavoro RAG. Nella pratica, questa strategia si allinea meglio con i compiti specifici del dominio e aumenta la pertinenza del modello in applicazioni con ambienti dati in rapida evoluzione.

Nell’esempio pratico, è possibile attivare/disattivare RAG per vedere l’impatto sulla qualità delle risposte dei modelli pre-addestrati con e senza il contesto fornito dai meccanismi di recupero intelligenti. Vedi Figura 10.

Sistemi RAG Operativi a Supporto di Motori di Inferenza di Qualità

Ora che abbiamo una comprensione fondamentale di RAG e del suo ruolo nelle applicazioni basate su modelli LLM, ci concentreremo sull’implementazione degli aspetti pratici e operativi di questi sistemi.

Come promesso, torniamo al Triangolo IQ (Figura 3) che sottolinea tre aspetti vitali dei motori di inferenza operativa di alta qualità. Analizzeremo l’opportunità di affrontare tutti e tre questi aspetti – scalabilità, latenza e fedeltà – utilizzando lo stack illustrato di seguito (Figura 6), che si concentra su un motore di inferenza composto da pipeline RAG e modelli fortemente ottimizzati in esecuzione su CPU.

Figura 6. Stack proposto del motore di inferenza - Immagine dell'autore

I Benefici Architettonici di RAG

Le applicazioni basate su RAG portano significativi benefici architettonici. Dal punto di vista della scalabilità, tutti i componenti centrati sui dati della pipeline convergono su un singolo (o pochi) database vettoriali (Figura 7), consentendo i vantaggi dei dati freschi di RAG di scalare bene con richieste crescenti/decrescenti degli utenti. Questo approccio unificato può migliorare significativamente la fedeltà delle risposte a compiti specifici del dominio semplificando notevolmente la gestione dei dati.

Figura 7. Diagramma di flusso dei dati semplice che mostra il flusso dei dati in un sistema AI basato su RAG - Immagine dell'autore

Modelli Ottimizzati: Efficienza e Prestazioni

I modelli possono ottenere una maggiore efficienza computazionale ed ambientale attraverso la compressione dei modelli e le tecniche di sintonizzazione dei parametri. Mentre la sintonizzazione può aiutare a personalizzare i modelli per compiti specifici, migliorandone l’accuratezza predittiva (fedeltà), i metodi di compressione come la quantizzazione possono ridurre le dimensioni dei modelli, migliorando significativamente la latenza dell’inferenza. Questi modelli snelli e sintonizzati sono più facili da implementare nel data center e consentono l’applicazione di intelligenza artificiale sul campo, aprendo la strada a vari casi d’uso innovativi.

Figura 8 - Immagine dell'autore

CPUs a Supporto di RAG

Per quanto riguarda i flussi di lavoro che coinvolgono logiche complesse, come RAG, le CPUs si distinguono per la loro ubiquità ed economicità. Ciò consente un’ottimizzazione della scalabilità poiché quasi ogni organizzazione può accedere a CPUs di livello enterprise nel cloud, a differenza degli acceleratori specializzati, che sono più difficili da ottenere.

Le moderne CPUs sono dotate anche di ottimizzazioni a basso livello – prendiamo ad esempio le Intel Advanced Matrix Extensions nei loro processori Xeon di quarta generazione – che migliorano la gestione della memoria e le operazioni su matrici nella fase di training e inferenza del deep learning. Il loro supporto per tipi di dati a precisione inferiore (come bf16 e int8) li rende adatti per ottenere una bassa latenza durante l’inferenza.

Inoltre, la compatibilità delle CPUs con i vari componenti di una pipeline RAG (Figura 9), compresi i database vettoriali e la ricerca intelligente (ad esempio, ricerca di similarità), semplifica la gestione dell’infrastruttura, rendendo più semplice ed efficiente il deployment su larga scala.

Figura 9. Adattamento della Figura 7. Mostra la capacità delle CPUs di supportare varie parti del sistema RAG. - Immagine dell'autore

Prima di proseguire, devo svelare la mia affiliazione con Intel e il prodotto utilizzato di seguito. Come Senior AI Engineer presso Intel, l’esempio pratico seguente viene eseguito su Intel Developer Cloud (IDC). Utilizzeremo IDC come modo gratuito e conveniente per accedere a Compute e acquisire esperienza pratica con i concetti descritti in questo articolo.

Esempio pratico: Implementazione di RAG con LangChain sulla Intel Developer Cloud (IDC)

Per seguire l’esempio pratico seguente, crea un account gratuito sulla Intel Developer Cloud e vai alla pagina “Formazione e workshop”. Nella sezione Gen AI Essentials, seleziona l’opzione Retrieval Augmented Generation (RAG) con LangChain. Segui le istruzioni sulla pagina per aprire una finestra JupyterLab e caricare automaticamente il notebook con tutto il codice di esempio.

Il notebook include dettagliate docstring e descrizioni del codice. Questo articolo tratterà delle meccaniche di alto livello fornendo contesto per funzioni specifiche.

Preparazione delle dipendenze

Iniziamo installando tutti i pacchetti necessari nell’ambiente base. Se preferisci, puoi creare il tuo ambiente conda, ma questa è un’opzione rapida e semplice per iniziare.

import sysimport os!{sys.executable} -m pip install langchain==0.0.335 --no-warn-script-location > /dev/null!{sys.executable} -m pip install pygpt4all==1.1.0 --no-warn-script-location > /dev/null!{sys.executable} -m pip install gpt4all==1.0.12 --no-warn-script-location > /dev/null!{sys.executable} -m pip install transformers==4.35.1 --no-warn-script-location > /dev/null!{sys.executable} -m pip install datasets==2.14.6 --no-warn-script-location > /dev/null!{sys.executable} -m pip install tiktoken==0.4.0 --no-warn-script-location > /dev/null!{sys.executable} -m pip install chromadb==0.4.15 --no-warn-script-location > /dev/null!{sys.executable} -m pip install sentence_transformers==2.2.2 --no-warn-script-location > /dev/null

Questi comandi installeranno tutti i pacchetti necessari nel tuo ambiente base.

I dati e il modello

Utilizzeremo una versione quantizzata di Falcon 7B (gpt4all-falcon-q4_0) dal progetto GPT4All. Puoi saperne di più su questo modello sulla pagina GPT4ALL nella sezione “Model Explorer”. Il modello è stato salvato su disco per semplificare il processo di accesso al modello.

La logica seguente scarica i set di dati disponibili da un progetto chiamato FunDialogues di Hugging Face. I dati selezionati verranno passati attraverso un modello di embedding e inseriti nel nostro database di vettori in un passaggio successivo.

def download_dataset(self, dataset):        """        Scarica il set di dati specificato e lo salva nel percorso dati.        Parametri        ----------        dataset : str            Il nome del set di dati da scaricare.        """        self.data_path = dataset + '_dialogues.txt'        if not os.path.isfile(self.data_path):            datasets = {"manutenzione robot": "FunDialogues/customer-service-robot-support",                         "allenatore di basket": "FunDialogues/sports-basketball-coach",                         "professore di fisica": "FunDialogues/academia-physics-office-hours",                        "cassiere di supermercato" : "FunDialogues/customer-service-grocery-cashier"}                        # Scarica il dialogo da Hugging Face            dataset = load_dataset(f"{datasets[dataset]}")            # Converti il set di dati in un dataframe di pandas            dialogues = dataset['train']            df = pd.DataFrame(dialogues, columns=['id', 'descrizione', 'dialogo'])            # Stampa le prime 5 righe del dataframe            df.head()            # Mantieni solo la colonna dei dialoghi            dialog_df = df['dialogo']                        # Salva i dati in un file txt            dialog_df.to_csv(self.data_path, sep=' ', index=False)        else:            print('i dati esistono già nel percorso.')

Nel frammento di codice sopra, puoi selezionare 4 diversi set di dati sintetici:

  • Manutenzione Robot: conversazioni tra un tecnico e un agente di supporto al cliente durante la risoluzione dei problemi di un braccio robotico.
  • Allenatore di Basket: conversazioni tra allenatori di basket e giocatori durante una partita.
  • Professore di Fisica: conversazioni tra studenti e professore di fisica durante gli orari di ricevimento.
  • Cassiere di Supermercato: conversazioni tra un cassiere di supermercato e i clienti.

Configurazione del modello

L’estensione GPT4ALL nell’API di LangChain si occupa di caricare il modello in memoria ed stabilire una varietà di parametri, come:

  • model_path: Questa linea specifica il percorso del file per un modello pre-addestrato.
  • n_threads: Imposta il numero di thread da utilizzare, che potrebbe influenzare l’elaborazione parallela o la velocità di inferenza. Questo è particolarmente rilevante per i sistemi multi-core.
  • max_tokens: Limita il numero di token (parole o sottoinsiemi di parole) per le sequenze di input o output, garantendo che i dati alimentati o prodotti dal modello non superino questa lunghezza.
  • repeat_penalty: Questo parametro possibilmente penalizza i contenuti ripetitivi nell’output del modello. Un valore superiore a 1,0 impedisce al modello di generare sequenze ripetute.
  • n_batch: Specifica il numero di batch per l’elaborazione dei dati. Questo può contribuire ad ottimizzare la velocità di elaborazione e l’utilizzo della memoria.
  • top_k: Definisce la strategia di campionamento “top-k” durante la generazione del modello. Quando genera testo, il modello considererà solo i k token successivi più probabili.
def load_model(self, n_threads, max_tokens, repeat_penalty, n_batch, top_k, temp):        """        Carica il modello con i parametri specificati per l'elaborazione parallela.        Parametri        ----------        n_threads : int            Il numero di thread per l'elaborazione parallela.        max_tokens : int            Il numero massimo di token per la previsione del modello.        repeat_penalty : float            Il penalità per i token ripetuti nella generazione.        n_batch : int            Il numero di batch per l'elaborazione.        top_k : int            Il numero di token "top k" da considerare nel campionamento.        """        # Il supporto dei callback supporta lo streaming token-per-token        callbacks = [StreamingStdOutCallbackHandler()]        # Verbose è richiesto per essere passato al gestore dei callback        self.llm = GPT4All(model=self.model_path, callbacks=callbacks, verbose=False,                           n_threads=n_threads, n_predict=max_tokens, repeat_penalty=repeat_penalty,                            n_batch=n_batch, top_k=top_k, temp=temp)

Costruzione del database vettoriale con ChromaDB

Il database vettoriale di Chroma è una parte integrante della nostra configurazione RAG, in cui archiviamo e gestiamo i nostri dati in modo efficiente. Ecco come lo costruiamo:

def build_vectordb(self, chunk_size, overlap):        """        Costruisce un database vettoriale dal dataset per scopi di recupero.        Parametri        ----------        chunk_size : int            La dimensione dei frammenti di testo per la vettorizzazione.        overlap : int            La dimensione di sovrapposizione tra i frammenti.        """        loader = TextLoader(self.data_path)        # Splitter di testo        text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=overlap)        # Incorpora il documento e archivialo in Chroma DB        self.index = VectorstoreIndexCreator(embedding= HuggingFaceEmbeddings(), text_splitter=text_splitter).from_loaders([loader])

Esecuzione del meccanismo di recupero

Al ricevere la query di un utente, utilizziamo la ricerca di similarità per cercare nel nostro database vettoriale dati simili. Una volta trovati un certo numero k di risultati corrispondenti, vengono recuperati e utilizzati per aggiungere il contesto alla query dell’utente. Utilizziamo la funzione PromptTemplate per costruire un modello e incorporare la query dell’utente insieme al contesto recuperato. Una volta che il modello è stato popolato, passiamo alla componente di inferenza.

def retrieval_mechanism(self, user_input, top_k=1, context_verbosity = False, rag_off= False):        """        Recupera frammenti di documenti rilevanti in base alla query dell'utente.        Parametri        ----------        user_input : str            L'input o la query dell'utente.        top_k : int, opzionale            Il numero dei primi risultati da restituire, per impostazione predefinita 1.        context_verbosity : bool, opzionale            Se True, vengono stampate informazioni di contesto aggiuntive, per impostazione predefinita False.        rag_off : bool, opzionale            Se True, disabilita la generazione migliorata attraverso il recupero, per impostazione predefinita False.        """        self.user_input = user_input        self.context_verbosity = context_verbosity                        # esegue una ricerca di similarità e recupera il contesto dai nostri documenti        results = self.index.vectorstore.similarity_search(self.user_input, k=top_k)        # unisce tutte le informazioni di contesto in una sola stringa         context = "\n".join([documento.page_content per documento in risultati])        if self.context_verbosity:            print(f"Recupero delle informazioni correlate alla tua domanda...")            print(f"È stato trovato questo contenuto che è più simile alla tua domanda: {context}")        if rag_off:            template = """Domanda: {question}            Risposta: Questa è la risposta: """            self.prompt = PromptTemplate(template=template, input_variables=["question"])        else:                 template = """ Non ripetere semplicemente il contesto seguente, usa la tua conoscenza in combinazione con esso per migliorare la tua risposta alla domanda: {context}            Domanda: {question}            """            self.prompt = PromptTemplate(template=template, input_variables=["context", "question"]).partial(context=context)

L’utilità LangChain LLMChain per eseguire inferenze basate sulla query passata dall’utente e sul modello configurato. Il risultato viene restituito all’utente.

 def esecuzione(self):        """        Esegue l'inferenza per generare una risposta basata sulla query dell'utente.        Restituisce        -------        str            La risposta generata.        """        if self.verbosity_contexto:            print(f"La tua Query: {self.prompt}")                    llm_chain = LLMChain(prompt=self.prompt, llm=self.llm)        print("Elaborando le informazioni con gpt4all...\n")        risposta = llm_chain.run(self.user_input)        return  risposta  

Sperimentazione Interattiva

Per aiutarti a iniziare rapidamente, il notebook include componenti ipywidget integrate. È necessario eseguire tutte le celle del notebook per abilitare questi componenti. Ti invitiamo a regolare i parametri e valutare l’impatto sulla latenza e sulla qualità della risposta del sistema. Ricorda, questo è solo un punto di partenza e una dimostrazione di base delle capacità di RAG.

Riassunto e Discussione

Nessuno vuole interagire con chatbot lenti e instabili che rispondono con informazioni false. Ci sono una pletora di combinazioni di stack tecnici per aiutare gli sviluppatori a evitare di costruire sistemi che comportano un’esperienza utente terribile. In questo articolo, abbiamo interpretato l’importanza della qualità dell’engine di inferenza per l’esperienza utente dal punto di vista di uno stack che consente scalabilità, fedeltà e vantaggi di latenza. La combinazione di RAG, CPU e tecniche di ottimizzazione del modello controlla tutti gli angoli del Triangolo IQ (Figura 3), allineandosi bene alle esigenze delle applicazioni di chat AI basate su LLM operative.

Alcune cose interessanti da provare potrebbero essere:

  • Modifica il modello di prompt trovato nel metodo retrieval_mechanism per creare prompt migliori in tandem con il contesto recuperato.
  • Regolare i vari parametri specifici del modello e di RAG e valutare l’impatto sulla latenza dell’inferenza e sulla qualità della risposta.
  • Aggiungere nuovi dataset significativi per il tuo dominio e testare la fattibilità dell’utilizzo di RAG per costruire le tue applicazioni basate su chat AI.
  • Il modello di questo esempio (gpt4all-falcon-q4_0) non è ottimizzato per i processori Xeon. Esplora l’utilizzo di modelli ottimizzati per piattaforme CPU e valuta i vantaggi di latenza dell’inferenza.

Grazie per la lettura! Non dimenticare di seguirmi per altri articoli come questo!