10 min čteníJohnny UnarJohnny Unar

Embedding drift vám potichu rozbije retrieval

Vector search se zhoršuje pomalu, a pak najednou. Měřte drift dřív, než spadne recall a upgrade modelu odpálí produkci.

jak tohle selhává v praxi

Většina retrieval systémů neselže dramatickým stack tracem. Rozpadnou se stejně jako zanedbaný databázový index, nenápadně, jednou neškodně vypadající změnou za druhou, dokud si někdo nevšimne, že výsledky hledání jsou nějak divné, support ticketů přibývá a půlka týmu týden ztrácí čas tím, že obviňuje kvalitu promptů. U embeddings se to děje obzvlášť snadno, protože je týmy berou jako mrtvá data v tabulce, typicky id, document, embedding vector(1536), možná updated_at, pokud byl někdo důsledný. Jakmile tohle schema skončí v Postgresu s pgvector 0.7.0 nebo se pošle do Pinecone či FAISS, všichni si to v hlavě odloží do šuplíku „vyřešená infrastruktura“.

Jenže tenhle mentální model je špatně. Embeddings jsou pohyblivý kontrakt mezi vaším obsahem, modelem, chunking logikou, normalizačním krokem a parametry indexu pod tím. Změníte OpenAI text-embedding-3-small na text-embedding-3-large, přepnete sentence-transformers all-MiniLM-L6-v2 na bge-large-en-v1.5, upravíte velikost chunků ze 400 tokenů na 900, přegenerujete corpus, který teď obsahuje mnohem víc boilerplate než před půl rokem, nebo omylem přestanete před ingestem dělat L2 normalizaci vektorů, a neudělali jste jednu změnu. Změnili jste geometrii celého systému.

Nejhorší je, že nearest-neighbor search pořád vrací výsledky. Žádná exception, žádný červený dashboard, žádný pager. Jen nižší recall, divnější ranking a víc sémanticky podobného balastu. U jednoho dokumentového pipeline, na kterém jsme ve Steezr pracovali, nebyl první symptom latency ani cena infrastruktury. Produktový manažer se zeptal: „Proč se při extrakci faktur pořád ukazují terms and conditions PDF nad skutečnými fakturami?“ ANN index byl v pořádku. Embeddings ne.

Embeddings musíte monitorovat stejně jako replication lag, p95 latency nebo failed jobs. Se stejnou vážností. Jinak se z retrieval stacku stane další systém, který technicky funguje až do chvíle, kdy přestane být k něčemu.

signály driftu, které fakt něco znamenají

Spousta rad k driftu je tak vágních, že jsou k ničemu. Typicky nějaké mávání rukou ve stylu „sledujte distribuce embeddings“, bez toho, co konkrétně počítat a jaký threshold už má být průšvih. Potřebujete malou sadu signálů, které se měří levně a mají přímou vazbu na kvalitu retrievalu.

Začněte mean cosine vůči stabilní referenční sadě. Držte si fixní canary set, třeba 1 000 dokumentů a 200 reprezentativních query. Znovu je naembeddujte aktuálně nasazeným modelem a porovnejte s předchozí produkční verzí. Když průměrná cosine similarity mezi starými a novými embeddings spadne z 0,98 na 0,84, není to akademický drift. Je to produkční incident, který teprve čeká, až se projeví. Přesný threshold závisí na modelech, ale velký propad po údajně drobném upgrade obvykle znamená změněné dimenze, jinou normalizaci nebo jinou embedding family s nekompatibilní geometrií.

Pak sledujte recall@k na canary query se známými správnými cíli. Tohle je signál, kterému věřím nejvíc, protože měří přesně to, za co uživatelé platí. Postavte si malý eval set, kde má každá query přiřazené relevantní document ID, a pouštějte ho denně nebo při každé ingest batchi. Když recall@10 spadne z 0,91 na 0,78, nepotřebujete filozofický seminář o vektorových prostorech. Potřebujete zastavit rollout. NDCG@10 se hodí taky, pokud řešíte kvalitu rankingu, ale recall chytá očividné průšvihy rychleji.

Další signál je distribuce embedding norm. Pro cosine search bývá prudká změna norm vektorů často známka chyby v preprocessingu. Viděl jsem pipeline, která potichu přepnula z normalizovaných vektorů na raw output a search se změnil v odpad, bez jediné chyby v kódu. Pokud jedete na pgvector s cosine distance, data by měla být před insertem konzistentně normalizovaná, nebo aspoň generovaná dost stabilně na to, aby se histogramy norm najednou nerozjely do stran.

Entropy se hodí ve chvíli, kdy se corpus v čase mění. Praktická aproximace je vzít rolling sample, seskupit ho do clusterů a měřit, jak moc se přiřazení koncentrují. Další možnost je kontrolovat variance po dimenzích nebo poměry explained variance z PCA. Když se embedding space začne hroutit do pár dominantních směrů, kvalita retrievalu obvykle padá s ním. Nepotřebujete metriku na úrovni PhD. Potřebujete detektor na „naše vektory nesou míň informace než dřív“.

Sledujte i duplicate-neighbor rate. Pokud spolu nesouvisející query čím dál častěji vracejí překrývající se top-k výsledky, prostor ztrácí rozlišovací schopnost. To je taky drift.

jak sampleovat, aniž byste rozbili prod

Nikdo nechce monitoring plán, který zdvojnásobí cenu inference. Dobrá zpráva je, že rozumné pokrytí získáte i s disciplinovaným samplingem a trochou SQL.

Vezměte rolling sample z nových dokumentů, další sample ze starších dokumentů, které se pořád hodně retrievují, a k tomu fixní canary set, který se nikdy nemění. Fixní canaries chytají regrese modelu. Rolling samples chytají drift v corpusu. Historické vzorky s vysokým trafficem pokrývají praktické scénáře, které uživatele skutečně bolí. Tyhle sady držte malé, aby se daly levně znovu embedovat, obvykle pár stovek až pár tisíc položek na segment podle objemu.

Pokud jste na Postgresu s pgvector, ukládejte k embeddings i metadata, abyste mohli data později segmentovat. Tohle typicky stačí:

sql
1create table document_embeddings (
2 document_id uuid primary key,
3 model_name text not null,
4 model_version text not null,
5 chunk_version text not null,
6 embedding vector(1536) not null,
7 embedding_norm real not null,
8 content_hash text not null,
9 created_at timestamptz not null default now()
10);
11
12create index on document_embeddings (model_name, model_version);
13create index document_embeddings_ivfflat
14 on document_embeddings
15 using ivfflat (embedding vector_cosine_ops)
16 with (lists = 100);

Pole model_version a chunk_version vám při incident response zachrání krk. Bez nich se každé vyšetřování driftu změní v archeologii.

Na lehké kontroly stačí denní job, který vytáhne 500 řádků z každého segmentu, spočítá summary stats a zapíše výsledek do tabulky s metrikami nebo rovnou do Promethea. V Pythonu je to nudný kód, a to je dobře:

python
1norms = np.linalg.norm(embeddings, axis=1)
2mean_norm = norms.mean()
3std_norm = norms.std()
4cos = np.sum(old_emb * new_emb, axis=1)
5mean_cos = cos.mean()

Pokud používáte FAISS, měřte sample recall proti brute-force baseline na malé podmnožině. Drift parametrů ANN je reálný taky. Někdo změní nprobe z 20 na 4, aby stáhl latency, dashboardy zůstanou zelené a kvalita search dostane tichý zásah. U Pinecone držte metriky rozdělené po namespace podle verze modelu a ingest batchí. Managed infrastruktura neznamená, že nepotřebujete viditelnost. Jen vám schová ovládací prvky do chvíle, kdy je potřebujete nejvíc.

Alertujte na trendy, ne na jednotlivé výkyvy. Třídenní pokles canary recall je reálný problém. Jeden hlučný podvečer nejspíš ne.

upgrade modelu bez rulety

Většina týmů řeší upgrade embedding modelu stejně jako bump front-end dependency, změní config, pustí backfill a doufá, že to dopadne dobře. To je dost bezohledné. Upgrade modelu mění samotný základ retrievalu, takže potřebujete rollout plán, rollback plán a dost metadat na to, abyste starý a nový systém porovnali vedle sebe.

Nejčistší pattern je dual write plus shadow eval. Nechte běžet aktuální produkční index, vedle něj vytvořte druhý index pro kandidátní model a nový obsah zapisujte do obou, zatímco historická data backfillujete na pozadí. V Postgresu to může znamenat paralelní tabulku, třeba document_embeddings_v2, protože míchat v jedné tabulce různé dimenze vektorů nebo různé předpoklady o distance je chaos. Ve FAISS postavte samostatný index file. V Pinecone vytvořte nový namespace nebo index. Query traffic držte na v1, zatímco v2 vyhodnocujete na canaries a nasamplovaných reálných query.

U každé nasamplované query porovnávejte překryv výsledků, recall, click-through, pokud ho máte, a latency. Ukládejte výsledky vedle sebe. Jestli kandidátní model zlepší úzký benchmark, ale zároveň zvedne duplicate-neighbor rate a sníží recall na provozních query, vyhoďte ho. Populární čísla z leaderboardů jsou k ničemu, pokud váš zákaznický portál začne při hledání faktur tahat dokumenty s refund policy.

Rollback musí být nudný a rychlý. Když držíte indexy odděleně, stačí feature flag v retrieval vrstvě. Něco jako RETRIEVAL_INDEX_VERSION=v1 v app configu musí během sekund přesměrovat všechny query zpátky na starý index. Pokud rollback vyžaduje cokoliv znovu embedovat, ten plán je špatně.

Pozor na částečné migrace. V testech vytvářejí falešný pocit výhry a v produkci bizarní chyby. Query embedovaná modelem A proti dokumentům embedovaným modelem B může pořád vracet uvěřitelné výsledky, a právě proto se ten bug hůř odhaluje. Přidejte tvrdou kontrolu kompatibility. Pokud má query dimenzi 1024 a cílový index 1536, failněte nahlas. Pokud model ID neodpovídá očekávaným metadatům, failněte znovu nahlas.

Tiché bugy v kompatibilitě jsou nejhorší. Product lidi je popisují jako „search je nějak mimo“, a mají pravdu.

dashboard, který bych fakt postavil

Použitelný dashboard pro embedding drift se vejde na jednu obrazovku. Cokoliv většího obvykle znamená, že se na to nikdo nepodívá, dokud už incident neběží.

Nahoře canary recall@5 a recall@10 podle verze modelu, plus sedmidenní trend. Ve druhé řadě mean cosine staré vs. nové embeddings na fixním canary setu, průměr norm, směrodatná odchylka norm a duplicate-neighbor rate. Ve třetí řadě provozní metriky: retrieval click-through, zero-result rate, pokud se do toho stavu aplikace umí dostat, p95 retrieval latency a počty ingestů rozdělené podle verze modelu. Přidejte jednu tabulku s query, která se zhoršila nejvíc, a jejich top-10 výsledky před a po změně. Tahle tabulka se čte víc než všechny grafy dohromady, protože inženýři potřebují příklady, ne dojmy.

Prometheus plus Grafana stačí. U menšího stacku stačí i plánovaný Django nebo Celery job, který zapisuje metriky do Postgresu, a Grafana si je vytáhne napřímo. Dělali jsme oboje. Pokud už aplikace běží na Next.js a Django, nevymýšlejte si separátní ML observability platformu, pokud to objem dat opravdu nevyžaduje. Většina týmů potřebuje víc disciplíny než nástrojů.

Ukázková sada alertů je jednoduchá: canary recall@10 spadne o víc než 5 % proti klouzavému 14dennímu průměru ve dvou bězích po sobě, mean cosine mezi aktuálním a předchozím modelem na fixních canaries klesne pod 0,92, směrodatná odchylka norm se zdvojnásobí proti baseline, duplicate-neighbor rate vyroste nad definované percentilové pásmo, ANN recall proti brute-force samplu spadne pod cíl po rebuildu indexu. Čísla si samozřejmě nalaďte na svůj systém, ale tvar alertů by měl zůstat jednoduchý.

Ještě jedna věc: pokud zatím nemáte označený canary set, udělejte nejdřív ten, než se pustíte do chytřejší matematiky kolem driftu. Retrieval systémy se hodnotí podle vrácených výsledků. Každá metrika musí být navázaná právě na tohle. Jinak skončíte s perfektním monitoringem systému, který se zhoršuje přesně v tom jediném směru, který uživatele zajímá.

Johnny Unar

Napsal/a

Johnny Unar

Chcete s námi spolupracovat?

Vector search se zhoršuje pomalu, a pak najednou. Měřte drift dřív, než spadne recall a upgrade modelu odpálí produkci.