pgvector e' un'estensione di
PostgreSQL molto interessante
perche' consente di memorizzare gli embedding ed effettuare efficienti ricerche
di similarita'.
Tecnicamente pgvector consente di memorizzare, interrogare ed indicizzare vettori.
Sembra un'astrusa tipologia di ricerca riservata ai soli matematici...
ma gli Embedding creati dai modelli di Machine Learning
sono proprio rappresentazioni vettoriali.
Percio' le ricerche effettuate con pgvector sono la base per
lavorare con le piu' attuali intelligenze artificiali generative.
Con pgvector e' possibile
fornire le funzionalita' richieste dalle AI generative
per le applicazioni utilizzando normali query SQL in PostgreSQL.
L'uso dell'estensione pgvector e' utile a chi si occupa di AI (Artificial Intelligence) e di RAG (Retrieval-Augmented Generation), ma vi e' un fortissimo e crescente interesse per queste funzionalita' e sempre piu' applicazioni ne faranno uso.
pgvector e' l'estensione PostgreSQL per le AI generative che consente la memorizzazione ed il confronto tra Embeddings con normali query SQL.
Su un qualsiasi Linux [NdA ed anche su MacOS] con un PostgreSQL recente possiamo installare l'estensione pgvector con:
Quindi collegati sul DB possiamo lanciare i comandi :
Gli Embeddings vanno generati con un modello, ad esempio, con le OpenAI API [NdE openai.Embedding.create(input=content, model="text-embedding-3-small") ] ed i dati ottenuti possono essere inseriti nelle tabelle con normali INSERT.
Ora che le strutture sono state create ed i dati sono stati caricati e' possibile interrogare la base dati con query SQL:
WITH pgv AS ( SELECT embedding FROM doc_embeddings JOIN doc USING (id) WHERE title like 'PostgreSQL%' ) SELECT title, author, content FROM doc_embeddings JOIN doc USING (id) WHERE embedding <=> (SELECT embedding FROM pgv) > 0.5;
Gia' fatto!
Ma se c'e' qualcosa che non e' chiaro... continuate a leggere!
Grazie alle recenti grandi innovazioni gli strumenti l'intelligenza artificiale (AI, Artificial Intelligence) sono in grado di creare risposte efficaci basate su modelli linguistici di grandi dimensioni (LLM, large language model). Per ottenere questo risultato l'AI viene addestrata, con un processo abbastanza lungo e costoso, attraverso un numero enorme di dati che sono la sua base di conoscenza.
Per fornire dati piu' aggiornati e, sopratutto, per includere informazioni
relative ad un particolare dominio (eg. i prodotti di un'azienda,
i manuali di un'apparecchiatura complessa, ...)
e' possibile arricchire il modello con informazioni con la
Retrieval-Augmented Generation (RAG).
L'idea di base della RAG e' quella di incrementare la domanda presentata al modello,
che resta lo stesso,
con un contesto ricavato dai dati aggiuntivi interni all'azienda.
I dati non strutturati possono essere di qualsiasi tipo e vengono
rappresentati come Embeddings.
Gli Embeddings tecnicamente sono dei vettori con centinaia di dimensioni
che rappresentano il significato dell'informazione contenuta.
Quanto piu' due Embeddings sono vicini quanto piu' hanno un significato simile.
Per un progetto RAG e' necessario creare un database
che contenga le informazioni di contesto da passare al modello
scegliendole tra quelle piu' attinenti alla richiesta.
pgvector e' un estensione di PostgreSQL che consente di memorizzare,
ricercare in modo efficiente e confrontare tra loro gli Embeddings.
Poiche' pgvector si basa su un potente database relazionale
ne eredita tutte le caratteristiche come le proprieta' ACID,
un completo linguaggio SQL, gli strumenti di amministrazione e tuning,
la scalabilita', ...
oltre alla possibilita' di mantenere, oltre agli Embeddings, tutti gli altri dati strutturati
organizzati come piu' opportuno alle applicazioni.
Per default pgvector effettua una ricerca esatta, tuttavia e' possibile creare indici specifici
[NdA al momento sono disponibili due differenti tipologie di indici per i dati vettoriali:
IVFFlat, HNSW]
per effettare ricerche approssimate molto piu' efficienti.
pgvector non fa parte delle core extension PostgreSQL e quindi tipicamente richiede un'installazione che tuttavia e' molto semplice. Su una macchina linux i comandi sono:
In caso di problemi o per maggiori dettagli basta fare riferimento alla
documentazione ufficiale.
Se si opera su un database in Cloud non e' possibile effettuare
l'installazione di una estension aggiuntiva ma...
la maggioranza dei Cloud provider ha gia' inserito pgvector
tra le estensioni disponibili quindi non e' necessario installarla!
Ora che l'estensione e' stata installata puo' essere utilizzata su qualsiasi database presente con:
Creata l'estensione sono disponibili nuovi datatype, indici e funzioni per operare con i vettori.
Una volta attivata l'estensione pgvector sono disponibili nuovi datatype, operatori, funzioni ed indici sulla base dati PostgreSQL come vedremo in questo capitolo.
Puo' essere utile ricordare alcuni datatype standard supportati in PostgreSQL: INTEGER 4 byte da -2G a 2G, REAL 4 byte con precisione 6 cifre decimali, DOUBLE PRECISION 8 byte con precisione 15 cifre decimali.
L'estensione pgvector introduce in PostgreSQL nuovi datatype:
Datatype | Max dimensions | Size | Note
vector | 16.000 | 4*dim +8 | Il datatype principale supportato dalla prima vesione di pgvector.
Consente la memorizzazione di vettori di REAL.
| halfvec | 16.000 | 2*dim +8 | Consente un numero maggiore di dimensioni nell'indicizzazione per i modelli che lo richiedono.
| bit | 64.000 | dim/8 +8 | Un numero molto elevato di dimensioni per valori con 0 o 1.
| sparsevec | 1.000 | 8*non-zero items +16 | Memorizza solo gli elementi diversi da 0.
| |
I datatype di pgvector sono adatti alla memorizzazione degli Embeddings.
Operatore | Funzione | Note
<-> | L2 distance | Distanza euclidea. Si calcola con il noto teorema di pitagora applicato ai vettori.
| <=> | cosine distance | Calcolo del coseno tra i due vettori. Normalizzato tra 1 e -1. Si calcola in modo efficiente.
| <#> | negative inner product | Prodotto tra i due vettori, da moltiplicare per -1.
| <+> | L1 distance | Manhattan distance. Di semplice calcolo perche' opera per differenza su ogni dimensione.
| <%> | Jaccard distance | Utilizzabile tra vettori binari. E' calcolato come rapporto tra la cardinalita' dell'intersezione e dell'unione e va tra 0 ed 1.
| <~> | Hamming distance | Utilizzabile tra vettori binari. Conta il numero di elementi diversi tra due vettori.
| |
Vediamo ora le principali funzioni con l'eventuale indicazione della versione da cui disponibili:
Funzione | Versione | Note
l2_distance(vector, vector) → double precision |
| cosine_distance(vector, vector) → double precision |
| inner_product(vector, vector) → double precision |
| vector_dims(vector) → integer |
| vector_norm(vector) → double precision |
| avg(vector) → vector |
| l1_distance(vector, vector) → double precision | 0.5.0 |
| sum(vector) → vector | 0.5.0 |
| l2_normalize(vector) → vector | 0.7.0 |
| binary_quantize(vector) → bit | 0.7.0 |
| subvector(vector, integer, integer) → vector | 0.7.0 |
| hamming_distance(bit, bit) → double precision | 0.7.0 |
| jaccard_distance(bit, bit) → double precision | 0.7.0 |
| |
Le funzioni principali sono quelle di distanza o di similitudine.
Questo consente di trovare gli embeddings piu' pertinenti alla ricerca effettuata.
Le funzioni di distanza classiche sono la L1/Manhattan distance e la
L2/Euclidean distance; pgvector le fornisce entrambe e ne aggiunge di ulteriori.
Le funzioni e gli operatori possono essere utilizzati sia nelle clausole di WHERE
che nell'ORDER BY.
Le differenti funzioni di calcolo delle distanze o delle similitudini
sono adatte a casi diversi e dipendono dal modello LLM,
spesso si utilizzano le formule piu' efficienti.
La Manhattan distance o distanza nella Taxicab geometry e' di semplice comprensione
per me che abito a Torino: le strade sono tutte ortogonali tra loro e la distanza
e' quella effettivamente richiesta per raggiungere la destinazione.
Per default pgvector esegue la nearest neighbor search in modo esatto, aggiungendo un indice e' possibile eseguire la ricerca in modo molto piu' veloce ma approssimato [NdA il termine esatto per il tipo di ricerca e' ANN: Approximate Nearest Neighbor search]. Le ricerche esatte di solito non servono... va comunque sottolineato che con la presenza o meno degli indici i risultati delle query possono cambiare.
L'estensione pgvector introduce in PostgreSQL nuovi tipi di indici:
Index | Note
IVFFlat | Indice a liste invertite
| HNSW | Indice a grafo multilayer
| |
L'indice va creato sulla funzione di distanza utilizzata nelle query (eg. vector_l2_ops, vector_cosine_ops). Il numero di dimensioni supportate dipende dal tipo di indice e dal datatype come riassunto nella seguente tabella:
Index | vector | halfvec | bit | sparsevec
IVFFlat | 2.000 | 4.000 | 64.000 | N.A.
| HNSW | 2.000 | 4.000 | 64.000 | 1.000
| |
Gli indici disponibili con pgvector hanno caratteristiche diverse tra loro.
Gli indici IVFFlat (Inverted File with Flat Compression) vanno creati solo dopo aver inserito i dati sulla tabella
perche' necessitano dei dati per la scelta delle liste invertite da creare.
Nella creazione dell'indice e' opportuno indicare il numero di liste.
Ad esempio: WITH (lists = 100) dove il valore puo' essere rows/1000 fino a 1M e sqrt(rows) oltre.
Se il numero di record nella tabella non e' sufficiente
e' possibile che le query non ottengano i risultati previsti;
in questo caso semplicemente basta cancellare l'indice.
Gli indici HNSW (Hierarchical Navigable Small Worlds) richiedono piu' memoria e sono piu' lenti da costruire ma piu' efficaci nelle ricerche. Gli indici HNSW possono essere creati anche con tabelle vuote. Nella creazione dell'indice e' opportuno indicare i parametri m (numero di connessioni per layer, default 16) ed ef_construction (dimensione dinamica per la costruzione del grafo, default 64). Ad esempio: WITH (m = 16, ef_construction = 128) che richiede tempi maggiori per la costruzione dell'indice ma migliora le ricerche rispetto al default.
Per utilizzare pgvector basta conoscere l'SQL.
Sono sfruttabili tutte le funzionalita' dell'SQL
anche quelle piu' avanzate o specifiche di PostgreSQL.
Tipicamente vengono utilizzati gli operatori di similitudine come condizioni di ricerca o
per l'ordinamento.
Dal punto di vista di gestione le query sui vettori sono identiche a tutte le altre: le query che utilizzano i vettori possono esssere analizzate con l'EXPLAIN, le query correnti sono riportate nella vista pg_stat_activity, gli statement vengono registrati da pg_stat_statements [NdA se configurato, come sempre suggerito su un database di produzione], puo' essere sfruttato il parallelismo dei thread nelle select, possono essere utilizzate le repliche, ...
In generale l'estensione pgvector non richiede un tuning particolare per essere utilizzata, tuttavia vi sono una serie di suggerimenti che possono essere utili per un uso efficiente.
Tutte le indicazioni relative al
tuning di PostgreSQL ed
all'ottimizzazione dell'SQL
sono ovviamente valide anche per pgvector.
In generale le impostazioni di shared_buffers, max_connections, effective_cache_size,
work_mem, max_wal_size sono i principali parametri di tuning di una base dati PostgreSQL
ed hanno un significativo impatto anche per l'estensione pgvector.
In particolare... gli indici IVFFlat e HNSW
possono essere creati senza lock in scrittura utilizzando la clausola CONCURRENTLY,
i tempi di creazione possono essere ridotti eseguendo il tuning del
parametro maintenance_work_mem
e le fasi di creazione possono essere controllate nella vista
pg_stat_progress_create_index.
E' anche possibile sfruttare il parallelismo nella creazione degli indici HNSW
impostando il parametro max_parallel_maintenance_workers.
In fase di creazione di un indice IVFFlat e' possibile indicare il
numero di liste da utilizzare con la clausola:
WITH (lists = 1000).
In fase di ricerca e' invece possibile indicare il numero di liste da esplorare
con l'impostazione del parametro
SET ivfflat.probes = 32
Un'impostazione consigliata per le lists dell'indice e' 1000 fino ad un milione di record
e poi la radice quadrata del numero di record per valori superiori.
L'impostazione consigliata dei probes e' la radice quadrata del numero di liste,
un valore superiore migliora la ricerca, un valore inferiore migliora la velocita'.
In fase di creazione di un indice HNSW e' possibile indicare due parametri:
il numero massimo di connessioni per livello ed il numero di
numero di liste candidate per creare il grafo con la clausola
WITH (m = 16, ef_construction = 64).
In fase di ricerca e' invece possibile indicare il numero di liste candidate
con l'impostazione del parametro:
SET hnsw.ef_search = 40;
La scelta della funzione della distanza dipende da diversi fattori.
Nel caso di OpenAI gli embedding sono normalizzati ad 1 come
lunghezza quindi la distanza euclidea (L2) e la distanza per coseno
ottengono gli stessi risultati.
Quindi come funzione di distanza si preferisce il calcolo
del coseno perche' piu' efficiente come calcolo
[NdA oppure prodotto dei vettori che e' praticamente equivalente].
Un consiglio suggerito dagli antichi romani: divide ed impera!
Quando le dimensioni iniziano a diventare significative diventa
importante separare i componenti ed ottimizzare ogni singola parte.
Quindi i dati vettoriali e' opportuno siano posti su tabelle dedicate
e, eventualmente le tabelle con dati vettoriali su un'istanza dedicata.
In questo modo la ricostruzione degli indici o il tuning del database diventano
piu' semplici da gestire.
Anche il partizionamento, l'utilizzo di indici parziali
e la riduzione del numero di dimensioni dei vettori
possono essere utilizzati efficacemente con pgvector quando le cardinalita' sono molto elevate.
Come ultimo il consiglio piu' semplice e valido anche per altri tipi di indice: e' sempre meglio creare gli indici dopo aver caricato tutti i dati.
LLM | Token limit
AWS Titan | 8.000
| Llama3 | 8.000
| GPT-4 | 8.192
| GPT 3.5 Turbo | 16.385
| Mistral | 32.000
| GPT-4 Turbo | 128.000
| GPT Vision | 128.000
| Cohere Command R | 128.000
| Claude3 | 200.000
| Gemini 1.5 | 1.000.000
| |
Per interagire con OpenAI, o con un qualsiasi altro modello LLM, bisogna utilizzare le API fornite. Spesso si utilizza il Python come linguaggio che e' di semplice comprensione e potente. Ma e' troppo lungo per parlarne...
Anche se in PostgreSQL si possono memorizzare testi di grandi dimensioni
in realta' i modelli hanno limiti molto piu' bassi definiti in token.
I token sono le unita' minime in cui vengono separate le parole
e vengono utilizzati perche' sono molto piu' efficienti dei caratteri per la
rappresentazione dei testi. In un testo in inglese indicativamente un token
corrisponde a 4 caratteri o ai 3/4 di una parola.
Differenti modelli hanno limiti diversi,
il limite sul numero di token non indica la qualita' del modello
ma e' una scelta di implementazione.
E' quindi spesso necessario trattare i testi da inserire sulla base dati
e da cui ricavare gli Embeddings.
Ma e' troppo lungo per parlarne...
La scelta degli embeddings con cui arricchire il contesto e' molto importante: quanti, con quale funzione di calcolo della distanza, con quali ulteriori condizioni. Anche considerare le sole informazioni testuali puo' essere una limitazione: a volte un'immagine vale piu' di mille parole! A tutti gli effetti gli Embeddings non sono limitati ai testi ma possono essere utilizzati per le immagini, per i suoni, in pratica in tutti i contesti in cui possono essere applicati i modelli di ML (Machine Learning). Ma e' troppo lungo per parlarne...
Troppo lungo da trattare qui, quindi semplicemente riporto alcuni link interessanti di cui suggerisco la lettura se interessati all'uso di pgvector in applicazioni AI: PostgreSQL as a Vector Database: A pgvector Tutorial, What is pgvector and How Can It Help You?, How to use PostgreSQL to store and query vector embeddings, ...
L'estensione pgvector e' disponibile dal 2021 ed ha avuto una significativa evoluzione in termini di funzionalita' incrementando il numero di dimensioni e le funzioni di confronto tra vettori. Questa paginetta e' aggiornata alla versione 0.7 disponibile dal 2024-04 perche' contiene parecchie nuove funzionalita'.
(Sources: Changelog )
|
|
|
|
|
0.7 | New datatypes: halfvec, sparsevec; new functions: binary_quantize, hamming_distance, jaccard_distance, l2_normalize, subvector; CPU dispatching on Linux x86-64, comparison for vectors with different dimensions, ... | 0.7.4 | 2024-04 | |
0.6 | Improved performance of HNSW, use external storage for vectors, ... Desupport: PG11 | 0.6.2 | 2024-01 | |
0.5 | HNSW index type, l1_distance fuction, sum() aggregate function for vectors, ... | 0.5.1 | 2023-08 | |
0.4 | Use extended storage for vectors (was plain), max dimensions for vector from 1024 to 16000, max dimensions for index from 1024 to 2000, avg() aggregate function for vectors, ... Desupport: PG10 | 0.4.4 | 2023-01 | |
0.3 | PG15, many fixes, ... Desupport: PG9.6 | 0.3.2 | 2022-10 | |
0.2 | PG14, (0.2.3 2022-01): indexing progress, ... | 0.2.7 | 2021-10 | |
0.1 | First release! | 0.1.8 | 2021-04 |
Per le versioni di PostgreSQL e' possibile fare riferimento a questa paginetta.
Anche se pgvector non e' una core extension moltissimi Cloud Provider hanno deciso di inserirla tra le estensioni preinstallate e quindi e' sufficiente il comando di CREATE EXTENSION per utilizzarla immediatamente.
PostgreSQL e' stato uno dei primi database relazionali a consentire la memorizzazione e la ricerca efficiente degli embeddings nella base dati in anticipo di quasi due anni rispetto alla maggioranza degli altri DB relazionali. Altri esempi di database Open Source che hanno inserito la fuzionalita' della vector search sono: ClickHouse, OpenSearch, Cassandra, ... E' importante riportare anche la nascita di diversi vector database, generalmente molto efficienti nella gestione degli Embedding ma a cui mancano spesso funzionalita' quali la gestione dei dati tradizionali, le transazioni, la replica, ... Tra questi riportiamo: Milvus, Vespa, Weaviate, Qdrant, ...
La documentazione in linea di pgvector e' molto completa e come la documentazione ufficiale riporta tutti i dettagli su PostgreSQL...
Titolo: pgvector
Livello: Intermedio
Data:
1 Aprile 2023
Versione: 1.0.3 - 15 Agosto 2024
Autore: mail [AT] meo.bogliolo.name