Photon: Construirea unui motor de regăsire și clasare de la zero
Cum formate de date specializate, citirile în lot și separarea construirii indexului de servirea interogărilor îmbunătățesc căutarea nativă pentru AI.
Introducere
Stiva de căutare nativă pentru AI a Perplexity preia și clasează paginile candidate, scoate la suprafață cel mai relevant conținut pentru ca modelul să își fundamenteze răspunsurile. Inițial, această etapă rula pe un motor open-source pe care l-am adaptat la sistemul nostru. Cu toate acestea, pe măsură ce indexul și sarcinile noastre de lucru au crescut, am atins limite stricte legate de costuri, latența la coadă și timpul de recuperare a nodurilor, pe care nu le-am putut rezolva în cadrul platformei terțe existente.
Pentru a depăși aceste provocări, am construit Photon, un nou motor intern de regăsire și clasare. Aceasta continuă abordarea noastră de a construi infrastructură de căutare orientată spre AI de la zero, așa cum am făcut cu CobbleDB, stocarea noastră distribuită de cheie-valoare pentru servirea înregistrărilor de pagini, și API-ul de căutare.
Photon folosește formate de date compacte pentru a citi și decodifica doar ceea ce are nevoie fiecare interogare, cu caching conștient de loturi și citiri asincrone pentru a reduce accesul la disc și a suprapune așteptările. Construirea indexului se execută separat de servirea interogărilor, actualizând un singur grup de servire la un moment dat și încălzindu-i cache-urile înainte de a rura interogări live către acesta.
Photon alimentează acum conductei noastre de căutare și reprezintă, de asemenea, fundația unei noi presetări rapide pentru API-ul nostru de căutare, cu o latență a apelului de căutare unică de 160 ms la p50 și 230 ms la p95. În șase benchmarkuri publice, presetarea rapidă a redus costul estimat model-plus-căutare per sarcină cu 68%, menținând în același timp o calitate agregată a sarcinilor comparabilă cu presetarea implicită. O lansăm astăzi pentru ca dezvoltatorii să poată accesa aceste îmbunătățiri.
Ce necesită motorul de regăsire și clasare
Pentru fiecare interogare, motorul de regăsire și clasare caută într-un index mare, distribuit, pentru a selecta și comanda un set mic de pagini relevante în limita bugetului de latență a stivei de căutare. De asemenea, sistemul trebuie să mențină indexul la zi pe măsură ce sosesc pagini noi și cele existente se modifică, fără ca această muncă să încetinească interogările live.
Am evaluat mai multe opțiuni open-source și am ales un motor matur de regăsire și clasare de uz general care a răspuns nevoilor noastre inițiale. Apoi l-am adaptat la arhitectura și sarcinile noastre de lucru, menținând latența și debitul stabile la scară de producție.
Unde a eșuat sistemul nostru anterior
Ne-am menținut propriul fork pentru a adapta motorul la indexul nostru în creștere, dar abordarea limitărilor cheie a impus tot mai mult reproiectarea unor mari părți ale platformei. Mai multe constrângeri s-au dovedit greu de rezolvat fără modificări arhitecturale fundamentale:
1. Costuri de scalare. Atât numărul de documente pe care le stocăm, cât și cantitatea de date per document pe care trebuie să le regăsim și să le clasam au crescut în timp. Această scalare a pus presiune pe hardware-ul nostru, în special pe cantitatea de memorie RAM disponibilă. Pentru a ține sub control costurile, am avut nevoie de reprezentări de date mai compacte și de un control mai fin asupra a ceea ce rămâne în memorie și a ceea ce este citit de pe disc la cerere.
2. Latență slabă la coadă. Pe traficul de producție, timpul de răspuns p99 al vechiului sistem era de aproximativ 800 ms. Setul de date depășea memoria RAM disponibilă, așa că nu am putut folosi mlock pentru a-l menține în întregime în memorie. Accesarea datelor non-rezidente putea declanșa defecte majore de pagină, blocând execuția interogărilor în timp ce sistemul de operare citea de pe disc.
De asemenea, motorul își comasa periodic indexul principal de pe disc cu indexuri mai mici eliminate din memorie. În timpul acestui proces de fuziune a indexului de pe disc, numărul defectelor de pagină a crescut și mai mult, latența p99 ajungând la aproximativ 1,2 s timp de 10-15 minute. Pe măsură ce cache-urile se încălzeau, frecvența defectelor de pagină și a latenței au scăzut ambele, sugerând că citirile reci au reprezentat o contribuție majoră la vârfuri. Preîncărcarea datelor și încălzirea indexului au redus vârfurile, dar nu au abordat în mod adecvat latența mare la coadă.
3. Recuperare și sincronizare de lungă durată. În sistemul anterior, implementarea unui cluster suplimentar și sincronizarea datelor sale putea dura mai mult de o săptămână. Sinkronizarea era, de asemenea, necesară atunci când se adăugau grupuri de noduri pentru a deține copii suplimentare de date sau la recuperarea după eșecuri de nod. În timpul recuperării, o pondere mai mare a răspunsurilor de căutare era parțială, ceea ce scădea scorurile calității răspunsului final.
În cele din urmă, am concluzionat că ar fi mai simplu și mai ieftin să construim de la zero decât să continuăm să adaptăm și să menținem bifurcația noastră. Bazându-ne pe experiența noastră în operarea sistemului anterior, o echipă mică de ingineri a lucrat alături de un roi de agenți de programare persistenți pentru a construi Photon.
Unde se integrează Photon în stiva de căutare
Photon este conceput pentru două fluxuri de lucru principale: servirea cererilor de căutare și preluarea actualizărilor din conducta de procesare.
O cerere de la serviciul de căutare de nivel superior ajunge la brokerul Photon prin intermediul unui echilibrator de sarcină. Brokerul selectează un grup de sharduri și redirecționează cererea către fiecare shard, monitorizând expirările de timp și emițând cereri suplimentare atunci când este necesar.
Fiecare shard efectuează regăsirea, clasarea inițială și clasarea de a doua etapă, apoi își returnează cei mai buni candidați către broker. Brokerul combină acești candidați într-o singură listă clasată și preia câmpurile cheie ale documentelor pentru paginile selectate de la shardurile relevante. Apoi returnează aceste rezultate serviciului de căutare de nivel superior, care colectează conținutul paginii necesar agentului pentru a genera un răspuns fundamentat.
Actualizările indexului urmează o cale separată. Pillar, stratul nostru de publicare, pregătește datele și le stochează în tabele YTsaurus. Indexorii convertesc aceste tabele în structuri de index pe care shardurile le pot încărca pentru căutare. Controlorul coordonează pregătirea datelor, construirea indexului și implementarea noilor versiuni de index în grupurile de servire. Înainte de a procesa traficul live, fiecare shard reia interogările de căutare reale pentru a-și încălzi cache-urile.
Proiectarea Photon pentru fluxurile noastre de lucru
Trei alegeri de design fac ca Photon să se potrivească bine cu fluxul nostru de lucru: reprezentări de date compacte, caching și citiri asincrone de pe disc, și separarea actualizărilor de index de servirea interogărilor.
Reprezentări de date pentru regăsire și clasare
Regăsirea și clasarea accesează datele documentelor în mod diferit. Regăsirea lexicală începe cu termenii de interogare și găsește documentele care îi conțin. Clasarea necesită apoi informații detaliate despre fiecare document selectat, inclusiv frecvențele și pozițiile termenilor.
Photon folosește două reprezentări complementare pentru a accepta aceste modele de acces. Un index inversat stochează o listă de documente potrivite pentru fiecare termen, numită listă de postări, pentru a susține selecția candidaților. Înregistrări separate per document furnizează datele necesare pentru clasare. De asemenea, optimizăm modul în care listele de postări sunt stocate și traversate pentru a îmbunătăți selecția sub un buget de lucru fix.
Reprezentările de date specializate ne permit să stocăm date cheie mai eficient. Un index inversat mai compact reduce cantitatea de date citite sau mutate per interogare, îmbunătățind latența prin eliminarea operațiunilor de I/O inutile. Aceste formate fac, de asemenea, posibilă verificarea dacă un document apare într-o listă fără a-l decodifica complet, o proprietate pe care se bazează logica noastră de traversare pentru a controla volumul de muncă al unei interogări sub un buget fix. Optimizarea înregistrărilor per document ne permite să stocăm mai multe date per document pentru clasare, reducând în același timp și costurile de scalare asociate sistemului nostru anterior.
Indexul inversat
Pentru fiecare termen, indexul inversat stochează o listă de postări. Aceste liste pot varia de la câteva documente până la milioane, astfel încât niciun format unic nu funcționează bine pentru fiecare listă. Photon alege formatul în funcție de numărul de documente care conțin termenul.
Listele scurte utilizează o reprezentare în linie care se încadrează într-o singură pagină și nu necesită un tabel de blocuri. Listele mai lungi care nu se încadrează în formatul în linie sunt împărțite în blocuri care acoperă intervale fixe de identificatori de documente. Reprezentarea fiecărui bloc depinde de densitatea sa: blocurile sparse stochează tablouri sortate de offseturi de ID-uri de documente, în timp ce blocurile dense folosesc hărți de biți (bitmaps). Blocurile sparse utilizează căutarea prin salturi (galloping search), care avansează în pași din ce în ce mai mari pentru a sări peste regiuni ale listei. Blocurile dense permit verificări de membru prin căutarea bitului corespunzător unui document.
Frecvența de interogare a unui termen determină locul unde este plasată lista sa de postări pe disc. Listele interogate frecvent sunt stocate aproape una de cealaltă, concentrând datele fierbinți într-o regiune de stocare mai mică. În cadrul fiecărei liste, documentele sunt ordonate după un semnal separat de utilitate, astfel încât candidații mai promițători să fie întâlniți mai devreme.
Traversarea listelor într-un buget de lucru
Selecția candidaților necesită traversarea listelor de postări pentru a găsi documente potențial relevante. Scanarea exhaustivă a fiecărei liste este costisitoare, așa că Photon utilizează un algoritm de tip WAND care traversează doar unele liste, luând totuși în considerare potrivirile din celelalte.
Algoritmul împarte listele în două grupuri. Listele motoare avansează traversarea și propun documente candidate. Listele de sondă contribuie la scorurile acilor candidați, dar nu generează ele însele candidați.
Scorarea candidaților combină acoperirea ponderată a termenilor de interogare și o contribuție a frecvenței, cu normalizarea lungimii documentului și o limită a câștigurilor din termenii repetați. Photon verifică mai întâi prezența termenilor în tablouri și hărți de biți pentru a limita scorul maxim posibil al fiecărui fișier candidat. Citește frecvențele exacte ale termenilor doar pentru candidații care încă ar putea îndeplini pragul de selecție.
Selecția se desfășoară apoi în trei etape:
- Traversare: Începeți cu o cerință strictă de acoperire a interogării. Dacă se găsesc prea puțini candidați, repetați cu o cerință relaxată.
- Retenție: Candidații sunt ținuți într-un set mărginit folosind un scor care combină acoperirea și frecvența. Pe măsură ce primii candidați colectați se îmbunătățesc, pragul de admitere crește.
- Selecția finală: Rezultatele finale pentru pas sunt alese din setul reținut pe baza scorurilor de frecvență. Fiecare câmp își rulează propriul pas, iar rezultatele sunt comasate folosind ponderile câmpurilor.
Algoritmul produce un set de candidați reprezentând documente relevante care vor fi utilizate în clasarea ulterioară.
Docblob
După ce paginile candidate sunt selectate, faza de clasare necesită frecvențe ale termenilor, acoperirea câmpurilor și locațiile și proximitatea aparițiilor. Obținerea acestor date prin intermediul indexului inversat ar necesita accesări dispersate în numeroase perechi termen-document. În Photon, stocăm o înregistrare compactă per document numită docblob care conține frecvențele termenilor, câmpurile în care a apărut un termen și unde a apărut în document. Drept urmare, clasarea unui candidat necesită o singură căutare per document.
Formatul este conceput pentru a evita decodificarea mai mult decât este necesar. În cadrul unui docblob, câmpurile sunt grupate după analizorul de text. Fiecare grup stochează o listă de termeni sortată și codificată cu Elias-Fano, astfel încât un termen de interogare să poată fi căutat direct fără a extinde mai întâi întreaga listă. O mască de câmp marchează unde a apărut un termen, un contor de frecvență urmărește frecvența, iar pozițiile reale se află în blocuri separate per pereche termen-câmp, codificate ca diferențe de poziție. Doar blocurile pentru termenii care s-au potrivit efectiv sunt decodificate, în timp ce tot restul înregistrării rămâne în formatul comprimat.
Această decodificare selectivă permite docblob să accepte stocarea mai multor date per document fără o creștere proporțională a costurilor. Creșterea înregistrării nu forțează mai multă muncă per căutare, deoarece clasarea atinge doar piesele relevante pentru termenii potriviți, în loc de întreaga înregistrare.
Citiri în lot și lucrul cu memoria
Chiar și cu reprezentările de date mai eficiente, memoria RAM se epuizează înaintea altor resurse, așa că trebuie să accesăm frecvent datele stocate pe disc. Photon utilizează citiri asincrone de pe disc și un cache optimizat pentru citiri în lot pentru a accelera aceste accesări, reducând timpul de așteptare per cerere. Acest lucru contribuie la o îmbunătățire semnificativă a latenței la coadă în comparație cu sistemul anterior.
Suprapunerea așteptărilor pe disc
Clasarea necesită obținerea caracteristicilor a o sută de documente situate în diferite părți ale unui fișier. Sistemul nostru anterior a folosit mmap pentru a mapa fișierul în spațiul de adrese virtual, astfel încât să poată fi citit prin pointeri de memorie. Cu toate acestea, accesarea unei pagini care lipsea din memorie necesita în continuare o citire de pe disc. Firul trebuia să aștepte datele înainte de a continua calculul, iar acest timp de așteptare se aduna atunci când procesul trebuia să citească înregistrări reci în mod secvențial.
Photon profită de faptul că offseturile înregistrărilor sunt cunoscute în avans, astfel încât putem trimite mai multe operațiuni de citire dintr-o dată. Acest proces este gestionat de reactor: fire dedicate trimit loturi prin io_uring și transmit datele citite codului apelant. Fiecare fir își menține propriul io_uring; loturile sunt trimise către reactorul cel mai puțin încărcat. Descriptorii de fișiere preînregistrați și pool-urile de buffere reduc suprasolicitarea citirilor repetate. Această execuție asincronă permite suprapunerea așteptărilor pentru mai multe operațiuni, reducând latența la coadă.
Reducerea numărului de accesări ale unității de disc
Înainte de a trimite citirile, Photon verifică întregul lot în cache-ul din memorie. Doar cererile pentru înregistrările lipsă merg pe disc; datele citite populează apoi cache-ul.
Cache-ul este conceput pentru citiri în lot. Mai exact:
- Nu există o alocare separată la un hit. În schimb, datele sunt copiate într-un buffer pregătit în avans de codul apelant.
- Nu există o achiziție de blocare de către cititor. În schimb, acesta verifică versiunea datelor și reîncearcă dacă intră în conflict cu o scriere.
- Folosește eliminarea CLOCK, în loc să necesite actualizări la o listă LRU partajată la fiecare citire.
- Folosește preluarea anticipată în cache-ul CPU, unde metadatele și datele pentru înregistrările viitoare sunt solicitate în avans.
Aceste optimizări scad latența fără a afecta performanța pe sarcinile de lucru care ne interesează.
Construirea și actualizarea indexului separat de servire
În Photon, construirea indexului și servirea interogărilor rulează pe noduri diferite. Indexorii transformă datele în structuri de stocare gata de utilizare, în timp ce nodurile de servire le citesc și efectuează regăsirea și clasarea. Drept urmare, construirea indexului nu concurează cu interogările de căutare pentru CPU, iar resursele pentru aceste sarcini pot fi scalate independent.
Această separare abordează blocajele de recuperare și sincronizare prezente în implementarea anterioară. Deoarece construirea indexului rula în același cluster care servea interogări, recuperarea unui nod eșuat sau adăugarea unei noi copii a datelor putea însemna reindexarea documentelor pe loc, un proces care dura zile și creștea răspunsurile parțiale în timpul recuperării. Photon elimină această cuplare; indexorii rulează ca joburi distribuite care produc structuri de index versionate, gata de servit, independent de nodurile de servire, astfel încât construirea următoarei versiuni a indexului nu mai concurează cu traficul live.
Pregătire și construire distribuită
O compilare începe prin selectarea documentelor pentru indexul activ și producerea exportului sursă. Controlorul înregistrează versiunea sursă, tabelele de intrare și numărul așteptat de sharduri. Joburile MapReduce din YTsaurus pregătesc textul, metadatele, fragmentele, caracteristicile și încorporările precalculate în tabele versionate partiționate pe shard. Fiecare indexor transmite fluxul tabelului pregătit, construiește structurile pentru shardul său și comasează segmentele de text. Indexul pregătit, împreună cu o descriere a componentelor sale, este încărcat în stocarea de obiecte.
Numărul de sharduri și numărul de indexori care rulează concomitent sunt configurate independent, astfel încât o compilare poate rula în mai multe valuri sau se pot aloca mai multe mașini pentru a lucra în paralel. Pregătirea distribuită și indexorii paraleli permit construirea întregului index web într-un număr de ore de o singură cifră, o îmbunătățire semnificativă față de sistemul anterior.
Implementarea unei noi versiuni
Înainte de implementare, controlorul verifică dacă noul index este gata. Dacă datele sunt incomplete, implementarea este blocată automat.
Atunci când există mai multe grupuri de servire, controlorul le actualizează pe rând. Mai întâi elimină un grup din rutarea brokerului și îl pornește pe noua versiune de index. Înainte de a servi cererile, shardurile trec printr-o etapă separată de încălzire bazată pe jurnalele de căutare, care prepopulează starea inițială și face ca tranziția către traficul real să fie mai lină. După încălzire, controlorul verifică starea indexului și disponibilitatea, apoi readuce grupul în rutare. Celelalte grupuri continuă să servească cererile în acest timp. Deoarece starea operațională este persistată, o repornire nu forțează reluarea unei compilări de la zero; controlorul se poate reatașa la o pregătire deja în curs, poate reutiliza un rezultat pregătit sau poate continua o rotație întreruptă de la pasul salvat.
Timpii de actualizare a datelor difere între verticalele de căutare. Pentru verticalele cu cerințe ridicate de prospețime, livrarea documentelor durează un număr de minute de o singură cifră. O copie suplimentară a datelor poate fi implementată în aproximativ o jumătate de oră; indexurile pregătite sunt încărcate și atașate la sharduri fără a reindexa documentele sursă.
Migrare și validare în cadrul stivei noastre de căutare
Primul nostru obiectiv a fost să înlocuim vechiul sistem cu un motor care a păstrat funcționalitatea și calitatea căutării, reducând în același timp timpii de răspuns. Am validat Photon în etape, începând cu teste de sarcină reproducibile și evaluări de calitate offline înainte de a trece la traficul live. Photon alimentează acum regăsirea și clasarea în stiva noastră de căutare de producție, având timpi de răspuns considerabil mai mici decât vechiul sistem.
Testarea sarcinii și evaluări
Pentru testarea performanței, am construit o unealtă de testare a sarcinii care reia interogări din jurnalele noastre de căutare la rate de sosire configurabile. Aceasta compară rezultatele între rulări și aplică teste statistice pentru a evalua dacă diferențele de performanță observate sunt consistente. Separat, am evaluat Photon și sistemul vechi pe aceleași seturi de date în sistemele noastre interne de evaluare offline pentru a verifica dacă există regrese de calitate.
După aceste verificări, am trimis interogări live către ambele motoare simultan, folosind trafic de umbră. Acest lucru ne-a permis să le comparăm performanța și rezultatele pe sarcinile de lucru de producție înainte de a schimba motorul care deservea utilizatorii. Apoi am activat Photon pentru subseturi mici de utilizatori și am extins treptat implementarea, monitorizând calitatea și performanța pe parcurs. Prima versiune a trecut testarea de acceptare fără regrese de calitate și am continuat implementarea până când Photon a servit tot traficul de producție.
Îmbunătățiri măsurate în producție
În fluxul nostru de lucru de producție, timpul de răspuns p99 a scăzut de la aproximativ 800 ms în sistemul vechi la aproximativ 65 ms în Photon. Această măsurătoare acoperă toate etapele din interiorul Photon, dar exclude etapele ulterioare din stiva de căutare de nivel superior. De asemenea, nu mai observăm vârfurile de latență asociate anterior cu comutarea versiunilor de index.
Photon folosește cu aproximativ 20% mai puține mașini de servire echivalente decât nodurile de conținut ale vechiului sistem, stocând în același timp de aproximativ 2,5 ori mai multe date per document. După migrare, am folosit aceste date suplimentare pentru a îmbunătăți calitatea clasării dincolo de cea a vechiului sistem.
Photon reduce, de asemenea, volumul de date care trebuie să rămână în memorie. Fixarea aceluiași set de date Photon în memoria RAM cu mlock ar necesita aproximativ de 4,6 ori mai multă memorie rezidentă decât utilizează în prezent Photon.
Evaluarea presetării rapide a API-ului de căutare
De asemenea, am folosit Photon ca bază pentru o nouă presetare rapidă pentru API-ul nostru de căutare, concepută pentru sarcini de lucru sensibile la latență. Conform măsurătorilor noastre, latența unui singur apel de căutare pentru presetarea rapidă este de 160 ms la p50 și 230 ms la p95, fiind mai mică sau comparabilă cu cifrele auto-raportate de alți furnizori de API-uri de căutare, deși percentilele de coadă raportate difere.
Am evaluat presetarea rapidă pe șase benchmarkuri bazate pe agenți: WideSearch, BrowseComp, DSQA, FRAMES, SEAL-0 și SEAL-Hard. În cadrul acestor evaluări, a înregistrat cel mai mic cost total estimat model-plus-căutare și cel mai ridicat scor agregat al sarcinilor dintre configurațiile testate.
Presetarea rapidă asociază Photon cu o clasare optimizată pentru fluxurile de lucru de tip agentic, oferind performanțe comparabile cu presetarea implicită la un cost substanțial mai mic. Totuși, aceasta vine cu un compromis în ceea ce privește calitatea generală a căutării. Presetarea rapidă utilizează mai puțină putere de calcul pentru clasare decât presetarea implicită. Pe benchmarkurile interne care acoperă interogări cu coadă lungă, acoperire largă și diversitate a rezultatelor, obține un scor cu 0,24 puncte mai mic la relevanță și cu aproximativ 3 puncte procentuale mai mic la disponibilitatea răspunsurilor. Considerăm că acesta este un compromis rezonabil pentru fluxurile de lucru bazate pe agenți, deoarece propriul raționament și cunoștințele modelului pot ajuta la compensarea diferenței.
Recomandăm presetarea rapidă pentru sarcinile agentice de zi cu zi. Pentru întrebări mai dificile sau ambigue, care sunt mai puțin sensibile la timp, presetarea implicită oferă o calitate mai robustă, justificând latența și costul mai ridicate.
Concluzie
Ne mutăm constant stiva de căutare pe o infrastructură internă bazată pe Rust. Înlocuirea motorului open-source cu Photon, motorul nostru personalizat de regăsire și clasare, este cel mai recent pas. Photon ne oferă control total asupra reprezentărilor datelor, a modului în care sunt organizate citirile și a modului în care sunt implementate noile indexuri, permițându-ne să optimizăm fiecare strat pentru sarcinile noastre de lucru.
Photon a făcut posibilă o căutare mai rapidă în cadrul produselor Perplexity și oferă fundația pentru noua noastră presetare rapidă din API-ul de căutare. În urma migrării, latența p99 pentru regăsire și clasare a scăzut de la aproximativ 800 ms la 65 ms. Formatele noastre de date compacte ne permit, de asemenea, să stocăm de 2,5 ori mai multe date per document, oferind regăsirii și clasării acces la informații mai bogate despre documente.
Presetarea rapidă oferă o latență de apel de căutare unică de 160 ms la p50 și 230 ms la p95. De-a lungul celor șase benchmarkuri bazate pe agenți evaluate, a redus costul estimat model-plus-căutare per sarcină cu 68% față de presetarea implicită, menținând în același timp o calitate agregată comparabilă a sarcinilor. Aceste rezultate demonstrează beneficiile practice ale infrastructurii proiectate în jurul sarcinilor noastre de căutare, atât pentru produsele Perplexity, cât și pentru dezvoltatorii care construiesc cu API-ul nostru.
Continuăm să îmbunătățim performanța și eficiența stocării pentru Photon. Scopul nostru este să acceptăm un index mai mare și date de documente mai bogate pe același hardware, menținând în același timp o căutare rapidă pe măsură ce sarcinile noastre de lucru cresc.
Citare
@online{perplexity2026Photon,
author = {{Perplexity} and Maksim Morozov and Bogdan Savko and Ruslan Kovalev and Aleksei Bykov and Artur Zayats and Denis Bykov and Nikita Kireev and Jarrod Bryan and Claire Beamer and Jeremy Yang},
title = {{Photon: Building a retrieval and ranking engine from scratch}},
date = {2026-09-24},
url = {https://www.perplexity.ai/hub/blog/photon}
}