Introduzione ai modelli multi-vettoriali con late interaction

Sentence Transformers è una libreria Python versatile per l'utilizzo e l'addestramento di modelli di embedding e reranker per applicazioni come la retrieval augmented generation, la ricerca semantica e molto altro. Con l'aggiornamento alla versione 6.0, la libreria introduce un quarto tipo di modello: il MultiVectorEncoder, destinato al retrieval con late interaction nello stile di ColBERT. Questa nuova funzionalità rappresenta un importante avanzamento nel panorama dei sistemi di information retrieval, permettendo agli sviluppatori di accedere a tecniche di ricerca più sofisticate attraverso un'interfaccia coerente e intuitiva.

Un aspetto particolarmente notevole è la compatibilità con diverse sorgenti di modelli pre-addestrati. Qualsiasi checkpoint PyLate e qualsiasi checkpoint Stanford-NLP ColBERT si caricano direttamente nel nuovo encoder, e i modelli della famiglia colpali-engine per il retrieval di documenti visivi possono essere utilizzati attraverso la stessa API familiare che gli utenti già conoscono per i modelli densi, sparsi e reranker. Questa unificazione dell'interfaccia riduce significativamente la curva di apprendimento e consente una transizione fluida tra diversi tipi di modelli di embedding.

La differenza fondamentale: compressione versus preservazione dei token

Per comprendere l'importanza dei modelli multi-vettoriali, è essenziale prima di tutto capire come funzionano i modelli di embedding tradizionali. Un modello di embedding denso legge un intero testo e ritorna un singolo vettore di dimensioni fisse, tipicamente di 384, 768 o 1024 numeri. Tutto ciò che il modello ha notato deve adattarsi in questi numeri fissi, e la similarità viene calcolata attraverso un singolo prodotto scalare tra due di questi vettori riassuntivi. Questo approccio funziona straordinariamente bene nella maggior parte dei casi, ma la compressione è lossy in un modo specifico e problematico.

In una compressione di questo tipo, un'entità rara, un identificatore esatto o una clausola cruciale in un passaggio lungo devono tutti competere per lo spazio all'interno dello stesso vettore. Una query con diversi requisiti simultanei si scontra con lo stesso muro. Per esempio, per "divano verde con gambe di legno e cuscini arrotondati", un singolo vettore deve mescolare tutti e quattro i requisiti in un unico punto nello spazio vettoriale. Di conseguenza, un divano verde con le gambe sbagliate finisce per trovarsi geograficamente vicino a quello che l'utente ha effettivamente cercato, causando risultati di retrieval inaccurati.

Un modello multi-vettoriale, noto anche come modello con late interaction o in stile ColBERT (dal paper ColBERT), salta completamente questa compressione problematica. Esegue lo stesso transformer di un modello denso, ma invece di raggruppare gli embedding dei token in un unico vettore, proietta ogni embedding di token su una dimensione più piccola (classicamente 128) e li conserva tutti. Un documento di 9 token diventa una matrice 9x128 anziché un vettore 1x128. Questo approccio preserva l'informazione a livello di token, permettendo una granularità molto più fine nella rappresentazione del contenuto.

L'interazione late: dove avviene la magia

L'interazione tra query e documento viene rinviata al momento dello scoring, il che è esattamente da dove deriva il nome "late interaction". Per comprendere questa differenza, è utile considerare le tre categorie principali di modelli di embedding:

  • Cross-encoder: interagiscono presto, con entrambi i testi che passano attraverso il modello insieme. Questo approccio è molto accurato ma non lascia nulla da precomputare, poiché ogni documento deve essere ri-codificato per ogni nuova query.
  • Bi-encoder (embedding densi): interagiscono appena, con un singolo prodotto scalare tra due riassunti già finiti. Questo permette di codificare una collezione una sola volta e interrogarla velocemente.
  • Late interaction (multi-vettoriali): si posizionano nel mezzo. I documenti vengono ancora codificati indipendentemente e possono essere indicizzati offline, ma lo scoring confronta ogni token della query rispetto a ogni token del documento, lasciando molto più spazio per l'interazione tra i due.

Lo scoring utilizza l'operatore MaxSim: per ogni token della query, prendi la sua similarità più alta rispetto a qualsiasi token del documento, poi somma questi massimi su tutti i token della query. Matematicamente:

MaxSim(Q,D) = ∑(Q_i ∈ Q) max(D_j ∈ D) Q_i · D_j

Poiché gli embedding dei token sono normalizzati L2, ogni prodotto scalare è una similarità coseno nell'intervallo [-1, 1], quindi la somma totale rientra nell'intervallo [-num_query_tokens, num_query_tokens]. Si può leggere l'operatore come un allineamento soft, dove ogni token della query punta al token del documento che meglio lo spiega, e il punteggio è quanto bene il documento supporta globalmente la query.

Vantaggi semantici e lessicali combinati

Un aspetto affascinante del late interaction è che l'allineamento non deve essere lessicale, poiché gli embedding dei token sono contestualizzati. Se codifichi "Where do penguins live?" contro "Penguins inhabit Antarctica." utilizzando il modello lightonai/mLateOn, il token della query "live" trova la sua migliore corrispondenza in "inhabit" con una similarità di 0.94, una parola con cui non condivide nemmeno un carattere! Questo è esattamente quello che il retrieval lessicale non può fare: BM25 e i suoi parenti hanno bisogno del termine stesso, quindi i sinonimi e le parafrasi slittano attraverso i loro filtri.

Naturalmente, anche i modelli di embedding densi colmano questo divario. Quello che il late interaction aggiunge è che lo fa senza rinunciare all'altra direzione. Quando una corrispondenza esatta è ciò che conta (un codice prodotto, un cognome, un nome di funzione), MaxSim ha comunque quel token seduto lì da solo, dove un modello a vettore singolo lo avrebbe dovuto mediare con tutto il resto. Inoltre, l'allineamento non è necessariamente uno-a-uno, poiché diversi token della query si stabiliscono regolarmente sullo stesso token del documento.

Questa dualità di capacità rappresenta un salto qualitativo nella ricerca semantica. Guadagni in qualità di retrieval sono particolarmente evidenti in diversi scenari:

  • Query con specifici pezzi di documenti rilevanti: quando una parte specifica di un documento è quello che lo rende rilevante.
  • Query multi-requisiti: come l'esempio del divano, dove ogni requisito può trovare la propria evidenza indipendentemente.
  • Dati out-of-domain: dove la compressione di un modello denso era sintonizzata per una distribuzione diversa, e il modello ha imparato a mantenere ciò che le query di addestramento necessitavano e a scartare tutto il resto, che potrebbe includere esattamente ciò che le query di produzione chiedono.

L'effetto del late interaction cresce con la lunghezza del documento, poiché più testo deve adattarsi nello stesso vettore fisso di un modello denso.

Il compromesso: dimensioni dell'indice

Naturalmente, non tutto è vantaggioso nei modelli multi-vettoriali. Il costo principale è la dimensione dell'indice. Un vettore per token anziché uno per documento è un numero molto maggiore di vettori, solo parzialmente compensato dalla dimensione più piccola. Codificare 4.874 passaggi Natural Questions con il modello lightonai/LateOn ha prodotto 608.414 vettori di token, una media di 124.8 per passaggio. Questo rappresenta circa 42 volte lo storage di un indice MiniLM, o 62 KiB per passaggio.

Tuttavia, gli indici sono spesso compressi. Gli stessi 608.414 vettori occupano 92 MB come indice fast-plaid, poiché PLAID memorizza un ID centroide più un residuo quantizzato per vettore anziché il vettore stesso. Per mettere in prospettiva, un modello denso 4096-dimensionale come Qwen3-Embedding-8B avrebbe bisogno di circa 80 MB per questi stessi 4.874 passaggi, quindi un indice multi-vettoriale compresso si posiziona nello stesso territorio degli indici densi che le persone già eseguono. La compressione rende il compromesso molto meno grave di quanto potrebbe sembrare a prima vista.

Strategie di ottimizzazione dell'indice

Oltre alla compressione degli indici, esistono altre strategie per mantenere le dimensioni sotto controllo senza sacrificare completamente la qualità:

  • Token Pooling: riduce il numero di vettori prima di qualsiasi archiviazione.
  • Retrieve and Rerank: evita completamente di costruire un indice, utilizzando invece il late interaction come fase di reranking su risultati ottenuti da sistemi di retrieval più semplici.
  • Truncamento dei documenti: molti modelli multi-vettoriali hanno limiti di lunghezza configurabili, anche se questo può comportare una perdita di informazioni per documenti molto lunghi.

La storia di PyLate e l'integrazione in Sentence Transformers

PyLate emerge più volte in questa discussione, quindi è utile comprendere il contesto storico. Sentence Transformers ha tradizionalmente gestito i modelli densi e sparsi ma non il late interaction. Per colmare questa lacuna, LightOn ha costruito PyLate sopra di esso, aggiungendo l'addestramento, l'inferenza e i pezzi di retrieval che questi modelli richiedono. Gran parte di ciò che si carica in questo post è stato addestrato con PyLate, e LightOn ha costruito un intero ecosistema attorno ad esso, incluso fast-plaid, l'indice late-interaction che appare nella sezione sull'indicizzazione.

Con la versione 6.0 di Sentence Transformers, queste capacità vivono nella libreria stessa, eliminando la necessità di dipendenze esterne e fornendo un'esperienza di sviluppo più coesiva. Questo rappresenta una democratizzazione significativa della tecnologia late-interaction, portandola in una libreria ben mantenuta con una comunità ampia.

Requisiti di sistema e installazione

Per iniziare con i modelli multi-vettoriali, i requisiti di sistema sono chiari e diretti. Sentence Transformers v6.0 richiede:

  • transformers v5.x
  • torch 2.2+
  • huggingface-hub v1.x

Se fissi una di queste versioni più bassa, è necessario pianificare l'aggiornamento in anticipo. Per il retrieval di documenti visivi nello stile ColPali, avrai bisogno anche delle dipendenze di immagine, che includono pacchetti come Pillow e potenzialmente librerie di elaborazione delle immagini aggiuntive.

L'installazione è semplice come un singolo comando: pip install -U sentence-transformers. Tutto ciò che segue in una sessione di sviluppo tipica funzionerà su questa base.

Caricamento e uso dei modelli multi-vettoriali

Caricare un modello multi-vettoriale assomiglia esattamente al caricamento di qualsiasi altro modello Sentence Transformers. Per trovare i modelli che funzionano, cerca i tag "multi-vector" e "sentence-transformers" sull'Hugging Face Hub. Qualsiasi modello con questi tag si carica con una singola riga di codice, indipendentemente dal fatto che la vita del modello sia iniziata come checkpoint PyLate, checkpoint Stanford-NLP ColBERT, o modello della famiglia ColPali per il retrieval visivo di documenti.

Il team sta lavorando attraverso l'ecosistema per aggiungere questo tag a ogni modello che funziona, quindi l'elenco continua a crescere. Underneath, MultiVectorEncoder legge ognuno dei formati in cui questi checkpoint sono stati pubblicati nel corso degli anni, quindi i checkpoint PyLate e Stanford-NLP si caricano direttamente anche quando il tag non è stato ancora aggiunto.

Un'eccezione importante riguarda i modelli di retrieval di documenti visivi. I checkpoint della famiglia ColPali vengono forniti nel formato proprietario di colpali-engine, che non contiene informazioni che Sentence Transformers può utilizzare direttamente, quindi ogni modello necessita di una piccola configurazione aggiunta al suo repository prima che si carichi. La maggior parte di questo lavoro è completata e in attesa di essere incorporata nei repository ufficiali.

Configurazione e ricette dei modelli

I modelli multi-vettoriali contengono una manciata di parametri di ricetta che differiscono per checkpoint: prefissi di marcatore per query e documenti, limiti di lunghezza, se le query vengono riempite con token [MASK], e quali token vengono ignorati durante lo scoring dei documenti. Tutti loro vivono nelle configurazioni del modulo, quindi print(model) mostra esattamente ciò che hai caricato.

Per esempio, il checkpoint originale ColBERTv2 riempie ogni query a esattamente 32 token e tronca i documenti a 180.