olivlawolivlaw

TEHNOLOGIE

Cum faci economie la API-urile de inteligență artificială — un clasificator local de știri, pe procesor

olivLaw Psychohistory
Rânduri de rafturi cu servere într-un centru de date.
Clasificarea știrilor nu are nevoie de un model care scrie, ci de unul care recunoaște. · Foto: Carl Lender, Wikimedia Commons, CC BY 2.0

Cea mai sigură economie la API-urile de inteligență artificială nu vine dintr-un model mai ieftin, ci din apelurile pe care nu le mai faci.

Etichetarea știrilor nu are nevoie de un model care scrie, ci de unul care recunoaște. Am mutat această muncă de pe un model generativ din cloud pe un sistem local, mic și verificabil, iar costul real nu a fost puterea de calcul, ci disciplina datelor: cine are voie să-l învețe și cum știi când greșește.

Analiza descrie arhitectura în linii mari, ce am încercat și a eșuat, ce a funcționat și lecțiile pe care le-am plătit pe parcurs. Nu publicăm cifrele interne de performanță: ele depind de fluxul nostru de știri și de o perioadă scurtă de măsurare, iar scoase din context ar spune mai mult decât știm.

Problema: o sarcină mică, făcută cu o unealtă mare

Știrile olivLaw primesc etichete de eveniment dintr-o listă închisă de șapte categorii: moțiune de cenzură, schimbare de coaliție, protest, acțiune militară, convocare de alegeri, eveniment judiciar și publicarea unui indicator macroeconomic. Pe ele se sprijină alte părți ale sistemului: importanța unei știri, impactul pe piețe, simulările și fișa „ⓘ” din flux.

Până acum, fiecare știre era trimisă unui model mare de limbaj, care citea textul și răspundea cu etichetele în format JSON. Soluția e comodă, fiindcă nu cere date de antrenare, dar are trei slăbiciuni. Depinde de furnizori externi, iar când unul cade sau limitează cererile, știrile se adună într-o coadă. Plătește pentru generarea de text o sarcină care nu are nevoie de generare. Iar fiecare răspuns trebuie verificat, fiindcă un model generativ poate întoarce orice.

Ce a eșuat: un model generativ local, pe procesor

Prima încercare de a aduce sarcina acasă a fost cea evidentă: același tip de model, dar unul deschis, de mărime medie, rulat local. Pe un server fără placă grafică, rezultatul a fost slab. Fiecare răspuns dura atât de mult încât depășea limita de timp, iar modelul ocupa procesorul atât de mult încât a încetinit alte sarcini grele de pe aceeași mașină, cum e sinteza vocală a podcasturilor.

Lecția a fost simplă: memoria cumpără un model mai mare, nu viteză. Pe procesor, un model generativ de mărime medie produce câțiva tokeni pe secundă, indiferent câtă memorie are mașina. Pentru o etichetă dintr-o listă închisă, a genera text, chiar și scurt, e drumul cel mai scump.

Arhitectura: două surse locale și un arbitru

Sistemul nou are trei piese, toate pe același server.

1. Un encoder mic, distilat dintr-un model mare. Encoderele sunt modele care nu scriu: transformă textul într-o reprezentare numerică și o clasifică direct. Am pornit de la mmBERT-small, un encoder multilingv publicat în septembrie 2025 de cercetătorii de la Johns Hopkins, cu licență MIT, antrenat pe peste 3.000 de miliarde de tokeni în peste 1.800 de limbi, româna inclusă. Are 140 de milioane de parametri, de aproape o sută de ori mai puțin decât modelul generativ local pe care îl încercaserăm. L-am antrenat pe etichetele puse deja de modelul mare, apoi l-am exportat într-un format portabil (ONNX), la precizie completă, ca să ruleze pe procesor, pe un număr limitat de fire de execuție.

2. O memorie de vecini. Fiecare știre din olivLaw are deja un vector, adică o „amprentă” numerică a sensului ei, calculată de un model de embedding care rulează local. Am pus într-un tabel separat știrile a căror etichetă e de încredere, împreună cu amprenta lor, și am indexat tabelul pentru căutare rapidă după asemănare. Când sosește o știre nouă, căutăm cele mai apropiate știri deja etichetate și le lăsăm să voteze. Vecinii mai apropiați cântăresc mai mult, iar cei prea îndepărtați nu votează deloc.

3. Un arbitru determinist. Cele două surse dau câte o probabilitate pe fiecare etichetă. Arbitrul le combină după ponderi fixate dinainte, aplică un prag pe fiecare etichetă și marchează drept incerte cazurile aflate aproape de prag. Dacă una dintre surse lipsește (de pildă modelul nu s-a încărcat), arbitrul lucrează cu cealaltă și o spune. Totul e cod obișnuit, fără model de limbaj, ușor de testat.

Rezultatul se scrie separat de eticheta oficială. Deocamdată, clasificatorul local rulează în umbră: vede aceleași știri, pune propriile etichete, iar noi comparăm continuu cele două variante. Trecerea lui în producție e o decizie editorială, nu una automată.

De ce o memorie, dacă există deja un model antrenat

Am măsurat ambele surse separat, pe aceleași știri. Singură, memoria de vecini e mai slabă decât un model antrenat. Nu e un clasificator mai bun, ci altceva, cu trei calități pe care modelul nu le are.

Prima: se adaptează fără reantrenare. Un subiect nou, de pildă un conflict care începe azi, intră în memorie odată cu primele știri etichetate sigur, iar știrile următoare îl găsesc imediat. Modelul l-ar învăța abia la următoarea antrenare.

A doua: își arată dovezile. Pentru fiecare etichetă, păstrăm cele mai apropiate știri care au votat. Un editor poate vedea de ce o știre a fost marcată „protest”: seamănă cu aceste cinci, care erau proteste.

A treia: primește corecții umane pe loc. O etichetă corectată de un om intră în memorie cu prioritate maximă și nu mai poate fi suprascrisă de niciun model.

Regula care ține sistemul cinstit

Un sistem care învață singur are o capcană cunoscută: dacă își pune propriile predicții înapoi în memorie, ajunge să-și confirme greșelile. O etichetă greșită atrage știri asemănătoare, care primesc aceeași etichetă greșită, care intră la rândul lor în memorie. Eroarea se întărește singură.

De aceea, în memorie intră doar două feluri de etichete: cele puse de un model mare pe care l-am declarat de încredere, după furnizor și versiune, și cele puse de un om. Predicțiile clasificatorului local nu intră niciodată. Lista de încredere stă într-un fișier de configurare revizuit, nu în cod, iar dacă memoria nu mai primește etichete noi, un semnal de monitorizare o spune.

Rețeta: cum faci economie la API

Ce am învățat se poate aplica oricărei echipe care plătește pe apel pentru clasificări, extrageri sau etichetări repetitive. Pașii, în ordinea în care aduc cel mai mult:

1. Separă recunoașterea de generare. Întreabă, pentru fiecare apel: răspunsul e o alegere dintr-o listă închisă sau un text nou? Alegerile (categorie, sentiment, limbă, „da sau nu”) sunt treaba unui clasificator, nu a unui model care scrie. Doar textele noi au nevoie cu adevărat de un model generativ.

2. Folosește modelul mare ca profesor, o singură dată. Lasă-l să eticheteze câteva mii de exemple reale, păstrează la fiecare etichetă modelul care a pus-o, apoi antrenează un encoder mic pe ele. Apelurile plătite devin o investiție care se termină, nu o chirie lunară.

3. Refolosește ce calculezi deja. Dacă ai deja embedding-uri pentru căutare, ai deja jumătate dintr-un clasificator. Un strat simplu peste ele, sau o căutare a celor mai apropiați vecini etichetați, costă aproape nimic.

4. Trimite la API doar cazurile incerte. Un model local care își cunoaște incertitudinea poate decide singur cazurile clare și poate trimite mai departe doar pe cele aflate aproape de prag. Așa plătești doar pentru întrebările grele, nu pentru toate.

5. Ce poate aștepta, trimite în lot. Furnizorii mari oferă API-uri asincrone pentru loturi: răspunsul nu vine pe loc, dar prețul e mult mai mic. La Anthropic, de pildă, lotul costă jumătate din prețul apelului obișnuit, iar majoritatea loturilor se termină în mai puțin de o oră. Rezumatele de noapte sau reetichetarea arhivei nu au nevoie de răspuns în secunde.

6. Nu plăti de două ori pentru aceeași întrebare. Aceeași știre preluată de zece site-uri, aceeași cerere reluată după o eroare: un cache după conținut și o cheie de idempotență elimină apelurile repetate.

7. Măsoară înainte să tai. Fiecare apel trebuie să lase o urmă cu sarcina, modelul, durata și costul. Fără ea nu știi ce merită mutat local și nu observi când un furnizor ieftin devine scump prin reîncercări.

Lecții învățate

Un profesor consecvent bate mai multe date. Etichetele vechi veneau de la mai multe modele, fiindcă lanțul de rezervă schimba modelul când unul cădea. Un model antrenat pe mai puține etichete, dar toate de la același „profesor”, a mers cel puțin la fel de bine ca unul antrenat pe de câteva ori mai multe etichete amestecate. Fiecare model are propriile obiceiuri despre unde trage granița dintre categorii. Dacă amesteci obiceiurile, elevul învață zgomotul. De atunci păstrăm, la fiecare etichetă, modelul care a pus-o.

Ce ai deja e adesea suficient. Prima tentație a fost o bază de date vectorială nouă, ca serviciu separat. Amprentele tuturor știrilor erau însă deja în baza de date principală, care știe să le caute. Un serviciu nou ar fi adus o a doua copie a datelor, sincronizare, backup și încă un punct de cădere. Am folosit ce exista.

Începe cu varianta cea mai simplă. Înainte să antrenăm modelul, am pus un strat liniar banal peste amprentele pe care le aveam deja. Antrenarea lui durează mai puțin de un minut. Modelul fine-tunat, care a cerut ore de calcul, a ieșit la început abia la nivelul lui, nu peste el. O bază simplă îți spune cât valorează de fapt efortul în plus și, de multe ori, e suficientă ca prim pas.

Categoriile rare sunt adevărata problemă. Protestele și acțiunile militare apar zilnic, iar modelul le recunoaște bine. Moțiunile de cenzură sau convocarea alegerilor apar rar, iar exemplele de antrenare sunt puține. Aici modelul local greșește mai des decât cel mare și, cel mai probabil, va avea nevoie de reguli explicite, scrise de om, pe un vocabular închis. Asta nu e un eșec al metodei: e locul în care judecata editorială valorează cel mai mult.

Acordul cu profesorul nu înseamnă adevăr. Tot ce am măsurat până acum e cât de des clasificatorul local spune același lucru ca modelul mare. Dar și modelul mare greșește. Pentru a ști cât de corecte sunt etichetele, avem nevoie de un set etichetat de oameni, independent de amândouă. Îl construim, iar până atunci formulăm rezultatele ca acord, nu ca acuratețe.

Măsoară pe mașina ta și verifică fiecare optimizare. Comprimarea greutăților la 8 biți aduce câștiguri mari pe procesoarele care au instrucțiuni dedicate și câștiguri modeste pe cele mai vechi, cum e al nostru. La noi a făcut mai rău: modelul comprimat a ieșit puțin mai rapid, dar a început să dea alte răspunsuri decât originalul, practic pe toate știrile. L-am prins doar pentru că am comparat deciziile celor două versiuni înainte de lansare. Rulează acum la precizie completă, puțin mai lent, dar identic cu modelul antrenat. Cifrele de viteză din articole și de pe paginile modelelor sunt măsurate pe alt hardware: le-am refăcut pe serverul nostru, cu numărul de fire limitat, în timp ce rulau și celelalte sarcini.

Greutățile unui model sunt date nesigure. Modelul de bază era publicat doar într-un format care, la încărcare, poate executa cod. L-am încărcat o singură dată într-un mod care acceptă doar tablouri de numere, l-am convertit într-un format fără cod executabil și am lucrat apoi doar cu acela, într-un container izolat.

Întâi umbra, apoi decizia. Clasificatorul local scrie separat, iar cinci semnale de monitorizare urmăresc dacă rulează, dacă rămâne în urmă, cât de des nu e de acord cu eticheta de încredere, dacă memoria mai învață și dacă modelul s-a încărcat. O schimbare care ar înlocui eticheta oficială nu există încă în cod. O vom scrie doar după ce acordul măsurat o justifică.

Lanțul cauzal

Cauza: o sarcină de recunoaștere (o etichetă dintr-o listă închisă) era tratată ca una de generare, de un model mare aflat la distanță. Mecanismul: un model mare etichetează o singură dată suficiente exemple, un model mic învață din ele, iar o memorie de exemple sigure acoperă subiectele noi și primește corecțiile oamenilor. Efectul: etichetele se pot pune local, la orice oră, fără să depindă de un furnizor extern, iar modelul mare rămâne pentru cazurile incerte. Contra-ipoteza: diferența de calitate pe categoriile rare ar putea rămâne prea mare, caz în care sistemul local rămâne o plasă de siguranță pentru căderi, nu înlocuitorul etichetei oficiale. O vom judeca pe setul etichetat de oameni.

Predicții verificabile

PredicțieOrizontProbabilitateCum se verificăSursa de verificare
Laboratorul care a publicat mmBERT (Johns Hopkins, CLSP) publică o versiune nouă sau un succesor al familiei de encodere multilingvepână la 30.06.202740–55%Model nou în organizația laboratorului sau articol nou pe arXivhuggingface.co/jhu-clsp, arxiv.org
Un encoder multilingv deschis, de cel mult 500 de milioane de parametri, raportează rezultate mai bune decât mmBERT-base pe XNLIpână la 30.06.202745–60%Tabelul de rezultate din articolul de lansarearxiv.org, huggingface.co
Modelul de bază mmBERT-small rămâne disponibil public sub licența MITpână la 31.12.202785–95%Pagina modelului și licența afișatăhuggingface.co/jhu-clsp/mmBERT-small

Metodă și limite

Metodă. Am antrenat modelul și am ales pragurile pe zile diferite de cele pe care l-am testat, ca rezultatul să descrie știri pe care modelul nu le-a văzut. Am comparat aceleași știri pentru fiecare variantă: amprentele știrilor cu un strat liniar simplu, memoria de vecini singură, modelul antrenat singur și combinația celor două. Probabilitățile din tabelul de predicții sunt estimări editoriale, în benzi de cel puțin zece puncte procentuale.

Limite. Perioada de măsurare e scurtă, de câteva zile, iar categoriile rare au puține exemple în ea. Măsurăm acordul cu modelul mare, nu corectitudinea. Rezultatele depind de amestecul nostru de surse și limbi (română, engleză și rusă). Diagnosticul ar fi infirmat dacă, pe setul etichetat de oameni, clasificatorul local ar greși sistematic mai des decât modelul mare și pe categoriile frecvente, nu doar pe cele rare.