Nota

SOAR

24 aprile 2026ilvallod3 min readIT

Utilizzando AlloyDB for PostgreSQL mi sono interessato all'algoritmo sottostante, basato su ScANN e recentemente esteso con SOAR.

In fase di indexing si costruisce una struttura per organizzare i vettori nello spazio, in modo da fare ricerca solo su sottoinsieme. I vettori vengono quantizzati e assegnati a diverse partizioni tramite SOAR. Analizziamo

Ho i dati in un database vettoriale e una query qq. Voglio ritornare i kk più vicini a qq. Facendo una ricerca lineare ottengo tutti dati esatti ma è inefficiente (intractable) data la maledizione della dimensionalità. Approssimo l'esattezza per guadagnare efficienza, da qui Approximate Nearest Neighbor

Spill Tree

Replicazione dei dati per ridurre errori di partizionamento.

Diagramma spill tree

Qui vediamo tt come un iperpiano di separazione, divide lo spazio in due regioni. Si introduce un overlap in modo i punti vicini al confine siano replicati sia in AA che in BB

Ma la complessità è esponenziale, se a ogni livello dell'albero un punto xx cade nella zona di overlap viene duplicato in entrambi i figli e comparirà 2h2^h volte.

Vector Quantization

Approssimo i valori dei vettori con dei centroidi. Questo processo ritorna due oggetti: codebook e funzione di assegnazione

C \in \mathbb{R}^{c\times d} \tag{1}

è una matrice di cc righe, ognuna un vettore in Rd\mathbb{R}^d, quindi ogni riga è un centroide.

\pi(v):\mathbb{R}^d \mapsto \{1,\dots, c\} \tag{2}

La funzione di assegnazione fa si che ogni vettore sia assegnato al centro più vicino. Così in fase di query calcoliamo il prodotto scalare tra qq e tutti i centroidi , ordiniamo per punteggio e recuperiamo i punti di quelle partizioni.

Questo non avviene gratis, perchè sostituendo xx con il suo centroide introduciamo dell'errore.

Residuo

Ciò che perdiamo sostituendo xx con il suo centroide

Più è grande e positivo più sottostima il punteggio reale e la partizione giusta può non essere visitata. Questo accade quando il residuo rr è allineato con la query qq

Spilling with Orthogonality-Amplified Residuals

Per evitare che un punto xx venga perso durante la ricerca lo assegniamo a più partizioni. Ma come scegliamo a quale altra partizione?

Diagramma residui ortogonali

A sinistra, vediamo che scegliendo cc', il nuovo residuo rr' ha una direzione simile a rr. A destra viene scelto cc'' tale che il residuo rr'' sia quasi ortogonale a rr

Così almeno una delle due approssimazioni mantiene un buon punteggio rispetto a qq. Per rispondere alla domanda precendete, scegliamo il centroide tramite una loss che minimizza l'errore di quantizzazione e penalizza l'allineamento tra residui.

L(r,r,Q)=EqQ[w(cosθ)q,r2]L(r', r, Q) = \mathbb{E}_{q \in Q} \left[ w(\cos\theta)\, \langle q, r' \rangle^2 \right]

dove:

  • r=xcr = x - c è il residuo della prima assegnazione
  • r=xcr' = x - c' è il residuo candidato
  • qq è una query Q\in Q1
  • θ\theta è l’angolo tra qq e rr
  • w(cosθ)w(\cos\theta) pesa di più i casi in cui l’errore originale è alto, quindi rr è allineato con qq

Tutte queste assegnazioni multiple aumentano i costi di memoria e indicizzazione in modo lineare con il numero di copie per punto. Viene dimostrato che già una seconda assegnazione è sufficiente a coprire la maggior parte dei casi, ulteriori assegnazioni portano benefici marginali rispetto ai costi.

Link al paper ufficiale: https://arxiv.org/pdf/2404.00774

Footnotes

  1. Si può considerare QQ come campione rappresentativi dei casi reali, la distribuzione delle query che ci aspettiamo durante l’uso del sistema

Nota: le opinioni espresse sono solo mie; non raccolgo alcun dato su di te.

Sull'uso degli LLM: li trovo affascinanti e utili in molti contesti, ma non li uso per scrivere al posto mio. Scrivere qui è un esercizio per capire cosa penso e i limiti del mio ragionamento: più impegnativo, più imperfetto, ma più onesto e reale.