Versió v0.13

Primer, els números

SIFT-1M — el benchmark ANN estàndard: un milió de vectors de 128 dimensions, 10,000 consultes reals, ground truth inclòs. Mateixa màquina, mateixa configuració, només va canviar el motor. I com que la construcció de l’índex d’Arkeion és determinista, totes dues versions entrenen exactament els mateixos clústers: el recall és idèntic byte a byte a cada punt de la corba. El que hi guanyes és velocitat pura:

| nprobe | recall@10 | v0.12 | v0.13 | | |—|—|—|—|—| | 10 | 0.850 | 2.73 ms | 1.10 ms | 2.5× | | 20 | 0.938 | 4.09 ms | 1.39 ms | 2.9× | | 50 | 0.988 | 9.12 ms | 2.48 ms | 3.7× | | 100 | 0.996 | 15.8 ms | 3.93 ms | 4.0× | | 400 | 0.997 | 58.9 ms | 11.7 ms | 5.0× |

El rendiment concurrent al punt d’operació recall@10 = 0.988 passa de 375 a 1,381 consultes per segon (8 fils, 4 nuclis). L’escaneig KNN exacte (sense índex) baixa d’1.34 s a 322 ms per consulta. L’índex vectorial s’encongeix de 38.8 MB a 25.4 MB (−35%).

Mil·lisegons per consulta — menys és millor v0.13 v0.12 recall 0.94 recall 0.988 recall 0.996 1.39 ms 4.09 ms 2.48 ms 9.12 ms 3.93 ms 15.8 ms 2.9× més ràpid 3.7× més ràpid 4.0× més ràpid 0 5 ms 10 ms 15 ms
SIFT-1M, un sol fil, consulta a consulta — mateixos clusters, mateix recall, només ha canviat el motor.

Res de la semàntica no s’ha mogut. Construccions deterministes, historial versionat, AS OF sobre la cerca: tot intacte. Els empats en una shortlist ara fins i tot es resolen de manera determinista (per rowid), independentment de l’ordre d’escaneig o del nombre de fils.

La pregunta de FAISS

La comparació que tothom vol de debò: FAISS, la biblioteca ANN de referència. Mateixa màquina, mateix SIFT-1M, configuració idèntica als dos costats — IVF amb 1,000 llistes, codis PQ16, rerank exacte sobre una shortlist ×32 (IVF1000,PQ16,RFlat amb k_factor=32 en termes de FAISS). Consulta a consulta, un fil:

| recall@10 | FAISS | Arkeion v0.13 | v0.12, com a referència | |—|—|—|—| | 0.85 | 0.42 ms | 1.10 ms | 2.73 ms | | 0.94 | 0.60 ms | 1.39 ms | 4.09 ms | | 0.99 | 1.32 ms | 2.48 ms | 9.12 ms | | 0.999 | 5.62 ms | 11.7 ms | 58.9 ms |

Recall@10 vs latència — escala logarítmica v0.13 FAISS v0.12 1.00 0.90 0.80 0.70 recall 0.99 FAISS v0.13 v0.12 0.5 1 2 5 10 20 50 mil·lisegons per consulta (log)
La mateixa pujada fins a recall 0,99: FAISS hi arriba en ~1,3 ms a la RAM, v0.13 en ~2,5 ms des d'un fitxer durador, v0.12 necessitava ~9 ms.

FAISS ho manté tot a la RAM i no promet res: sense durabilitat, sense transaccions, sense versionat, sense xifratge, sense SQL — si el procés mor, reentrenes. Arkeion serveix el mateix recall des d’un fitxer xifrat, versionat i a prova de caigudes, en dos mil·lisegons en comptes d’un. Abans de la v0.13 aquesta escletxa era de 7–10×; ara és el preu de ser una base de dades, i creiem que és el preu correcte. (Una nota metodològica: FAISS sense l’etapa de rerank exacte topa amb un sostre de recall 0.56 en aquesta configuració — si has vist per aquí xifres d’IVFPQ sospitosament ràpides, comprova-ho.)

Què s’ha publicat

Postings en blocs. L’índex antic guardava una cel·la de b-tree per vector: cada candidat pagava una visita completa a la cel·la —descodificar, comprovar la clau, callback— abans i tot de calcular-ne la distància. A la v0.13 cada clúster guarda blocs columnars de codis i rowids (~3 KB per cel·la), de manera que el kernel de distància recorre memòria contigua i el peatge del b-tree s’amortitza al llarg d’un bloc. Aquest és el canvi de format que hi ha darrere tant de la velocitat com de l’índex més petit. Els teus índexs vectorials existents continuen funcionant intactes en el format antic; REBUILD VECTOR INDEX els migra quan tu ho decideixis.

El KNN filtrat fa servir l’índex. La consulta real més comuna —WHERE tenant_id = ? ORDER BY distance LIMIT k— solia caure en un escaneig complet exacte, perquè una clàusula WHERE desactivava del tot el pla vectorial. Ara el planificador sobremostreja la shortlist, filtra fila a fila, escala a tots els clústers si el filtre és voraç, i només recorre a l’escaneig exacte quan ni així pot garantir k supervivents. Correcte per construcció; ràpid en el cas comú.

Una palanca que faltava. CREATE VECTOR INDEX … FACTOR n controla quant sobremostreja l’índex abans del rerank exacte (per defecte 32, l’antic valor hardcodejat). Uns bons codebooks PQ estan contents amb 8 —una quarta part de les lectures de fila—; les dades difícils en poden demanar més. Ara és una decisió per índex, no nostra.

Els silenciosos. El camí SQL cacheja els centroides descodificats tal com l’API ja feia sempre (~2 ms de cada consulta, fora). Els cursors del b-tree baixen dins de la pàgina en comptes de materialitzar cada node intern — això sol va portar l’escaneig de clúster de 2.9 ms a 1.2 ms, i va accelerar el KNN exacte com a efecte secundari. Els encerts de la memòria cau de pàgines ja no toquen el lock global del pager, que és on s’amagava l’escalat concurrent. Una taula de lookup ADC plana, un heap de candidats acotat, un rerank paral·lel per al camí sensible a la latència.

En què gasta el temps una consulta ara metadades de l'índex centroides + codebooks PQ descodificat un cop, a la cau abans ~2 ms a cada consulta SQL · ara 0 recorregut de clusters postings per blocs · LUT plana heap acotat · poda per radi descensos b-tree en pàgina 2.9 → 1.2 ms rerank exacte k × FACTOR candidats només la columna de vectors llegits en ordre de rowid paral·lel, opcional 9.12 ms → 2.48 ms d'extrem a extrem SIFT-1M · recall@10 = 0.988 · un sol fil
Cada etapa va mantenir les seves garanties: construcció determinista, rerank exacte, historial versionat.

El que ens vam negar a publicar

Perfilem abans d’optimitzar, i mesurem abans de fer merge. Tres idees van morir així, i creiem que val la pena publicar el cementiri:

  • Un multi-get de cursor compartit per al fetch del rerank: elegant, i 20× més lent amb rowids dispersos. Diagnosticar-ne el motiu va portar directament a l’arranjament del descens dins de la pàgina de més amunt — el prototip es va pagar sol, i després es va esborrar.
  • Un kernel SIMD enter per a escaneigs int8: 1.01×. LLVM ja autovectoritza el camí en float; l’error de quantització extra no va comprar res.
  • Fastscan de 4 bits a l’estil FAISS: un prototip AVX2 real amb shuffle_epi8 va mesurar 1.9× — sobre un kernel que ara és ~15% de l’escaneig. Un canvi de format més una exempció de codi unsafe a canvi d’un ~8% de punta a punta és un mal tracte. El nostre ADC escalar corre a 8 ns per candidat; hi tornarem quan la resta de l’escaneig l’atrapi.
  • Paritat total amb FAISS. Sabem com tancar el 2× restant: mantenir una còpia plana de cada vector dins de l’índex (el que FAISS anomena RFlat) perquè el rerank exacte no toqui mai l’arbre de files. Funciona — i costa +512 MB per milió de vectors a 128 dimensions, +3 GB per milió a 768. Vam decidir que un mil·lisegon és més barat que un gigabyte: l’índex de 25 MB es queda, el mil·lisegon extra es queda, i el teu disc continua sent teu. Si algun dia les càrregues de treball reals hi discrepen, pot arribar més endavant com a opció activable — mai com a valor per defecte.
El cementiri, mesurat — guany d'extrem a extrem sense guany real funciona, massa car 1.0× = sense canvi multi-get amb cursor compartit kernel enter int8 fastscan de 4 bits acompanyant de rerank pla 0.19× lectura 20× més lenta amb rowids dispersos 1.01× — LLVM ja ho havia vectoritzat 1.08× — kernel 1.9× en el 15% del recorregut 1.9× · +0.5 GB per M vectors 0.5× 1.5× les barres arrenquen a 1.0× — a l'esquerra de la línia és una regressió
Quatre idees, quatre mesures, un supervivent per mèrits propis — mort per la seva pròpia factura de disc.

Una base de dades que et promet un historial demostrable també hauria de demostrar les seves afirmacions de rendiment. Cada número de més amunt ve d’un benchmark reproduïble, i cada optimització rebutjada ve amb la mesura que la va matar.

Actualitzar

cargo update -p arkeion a 0.13.0. Els fitxers existents s’obren com sempre; els índexs vectorials existents serveixen consultes en el seu format actual. Executa REBUILD VECTOR INDEX <name> per índex quan vulguis el format de blocs i la nova velocitat — és un esdeveniment discret i determinista, com cada construcció a Arkeion.