Kera și munca de coborâre

O poveste despre compilator și munca nevăzută dintre un graf curat și o mașină reală: loweruire deterministă, control asupra targetului și dovezi care...

Kera și munca de coborâre

Raportul de bug care nu era un bug

Povestea începe de obicei cu un număr care este aproape corect. Nu complet greșit. Nu stricat într-un mod care face tabloul de bord roșu. Aproape corect în modul scump: genul de greșeală care permite unei echipe să livreze, alteia să ezite, iar unui auditor să se întrebe de ce s-a schimbat răspunsul când același model a fost mutat de pe o mașină pe alta.

O compilare rulează pe un server x86 și produce o valoare. Următoarea compilare rulează pe un laptop ARM și produce o valoare suficient de apropiată încât o demonstrație să funcționeze. O cale GPU este mai rapidă, dar rotunjește diferit prin calcul. O cale FPGA este atractivă pentru sincronizare, dar dintr-o dată echipa de software poartă o discuție despre hardware. Toată lumea poate explica o mică parte din diferență. Nimeni nu deține întregul traseu de la sursă la rezultat.

Acesta este spațiul pentru care a fost construit Kera. Nu ideea de model, nu slide-ul de marketing, nu o lucrare despre un compilator viitor, ci munca de compilator care începe odată ce o echipă spune că același calcul trebuie să ruleze pe mașini diferite și să însemne totuși același lucru. Această obligație are nevoie de un responsabil. Promisiunea compilatorului nu este că lowering-ul este elegant. Promisiunea este că lowering-ul este tratat ca muncă.

Lowering-ul este partea dintr-un compilator în care intenția frumoasă a limbajului devine instrucțiuni pe care o țintă le poate executa. Este și locul unde afirmațiile vagi devin vizibile. Dacă rezultatul trebuie să fie identic bit cu bit pe CPU, GPU, FPGA și WebAssembly, atunci compilatorul nu poate ignora diferențele dintre ținte. Trebuie să transporte suficientă structură pentru a decide ce se poate schimba și ce nu trebuie schimbat. Trebuie să cunoască spațiile de memorie, efectele, opcodurile, mișcarea datelor, limitele țintei și dovezile care confirmă că două compilări sunt același calcul.

Versiunea tentantă a poveștii este să numim asta un strat de portabilitate. Este prea puțin. Portabilitatea spune că programul rulează și în altă parte. Kera vizează afirmația mai dificilă: programul este reprezentat ca un graf cu adresare prin conținut, compilat într-un artefact .keg, redus la mai multe backend-uri și produce totuși același răspuns. Bucătăria se schimbă. Rețeta nu.

Mijlocul pe care nimeni nu vrea să îl vândă

Prima conversație cu un cumpărător serios rareori începe cu sintaxa. Începe cu o mizerie. Există un model care contează. Există o simulare care a fost cercetare și a devenit operațională. Există un calcul de risc al cărui rezultat nu mai are voie să varieze în funcție de mașină. Există o implementare la periferie care nu poate transporta întregul runtime cloud. Există o echipă care vrea un accelerator, dar nu își permite o rescriere de fiecare dată când acceleratorul se schimbă.

Cele mai multe instrumente fac acest lucru să pară o problemă de implementare. Alege ținta, exportă modelul, corectează runtime-ul, acceptă o anumită variație, apoi scrie un document care explică excepțiile. Documentul crește. Matricea de testare crește. Numărul de specialiști crește. În cele din urmă, organizația plătește de două ori pentru eterogenitate: o dată când cumpără hardware-ul, a doua oară când încearcă să demonstreze că hardware-ul a făcut aceeași muncă.

Kera pornește de la capătul opus. Pagina Kera îl numește un limbaj de sistem tipizat static, cu un IR nativ pentru grafuri și cu adresare prin conținut. Această frază contează pentru că graful nu este o diagramă decorativă de compilator. Este obiectul executabil. Codul sursă este redus în grafuri aciclice direcționate de operații, stocate ca fișiere .keg. Fiecare nod poartă o structură definită. Munca duplicată poate fi eliminată structural. Același graf poate fi redus pentru CPU, GPU, FPGA și WASM fără a trata fiecare țintă ca un univers separat și mic.

De aceea Kera trebuie să se comporte ca o infrastructură serioasă de compilator, nu ca o diagramă. Zona incomodă de mijloc este acolo unde trăiește clientul. Tokenizarea trebuie să păstreze pozițiile octeților. Parsarea trebuie să alimenteze atât uneltele, cât și compilarea. Verificarea tipurilor trebuie să respingă nepotrivirile de formă și de muchii înainte ca acestea să devină evenimente la runtime. Verificarea împrumuturilor trebuie să înțeleagă proprietatea asupra memoriei gazdă, de dispozitiv, fixată și unificată. Optimizarea trebuie să fie repetabilă. Serializarea trebuie să ofere organizației un artefact pe care să-l poată păstra.

Nimic din toate acestea nu face un titlu senzațional. Este mult mai util decât un titlu. Înseamnă că, atunci când o sarcină de lucru trece de la dezvoltare la producție, sau de la o bază CPU la o cale GPU, compilatorul are ceva stabil cu care să compare: amprenta grafului. Dacă amprenta se potrivește, munca este aceeași muncă. Acesta este un punct de control comun pe care un inginer, un cumpărător și un auditor îl pot înțelege cu toții.

Un graf este o chitanță, nu o imagine

Modul vechi de a explica un program este să arăți codul sursă și să ceri cititorului să aibă încredere în compilator. Pagina Kera revine mereu la un alt obiect: graful. Fiecare operație este un nod. Muchiile exprimă dependențe. Graful are conținut. Conținutul are un hash. Hash-ul devine o amprentă care călătorește prin procesul de compilare.

Graful este obiectul durabil: sursă, tipuri, regiuni, hash-uri și artefactul care călătorește mai departe.

Acest lucru sună ca un mecanism intern până când stai lângă o echipă de conformitate. O echipă de conformitate nu vrea să știe că un furnizor are o arhitectură frumoasă de compilator. Vrea să știe dacă calculul aprobat luna trecută este calculul care rulează astăzi. Amprenta grafului este puntea dintre aceste lumi. Nu este o captură de ecran a compilatorului. Este un mod compact de a spune: acest set de operații, aceste tipuri, aceste intrări, aceste atribute, această structură de dependențe.

Modelul de noduri este deliberat strict. Un nod este o versiune de schemă, un opcode, descriptori de ieșire, hash-uri de intrare și hărți de atribute aranjate în ordine canonică. Octeții sunt supuși unui hash cu SHA3-256 sub prefixe separate pe domenii, astfel încât hash-urile de nod, de regiune și de graf trăiesc în spații de nume separate. Dacă două noduri au același opcode, aceleași intrări și aceleași atribute, ele au același hash. Inserarea unui duplicat returnează intrarea existentă. Eliminarea subexpresiilor comune devine o consecință a reprezentării, nu o etapă eroică adăugată ulterior.

Acesta este genul de detaliu care transformă un sistem de inginerie în ceva pe care oamenii se pot baza. Un cumpărător nu cumpără o diagramă de compilator. Un cumpărător cumpără mai puține dispute despre dacă un calcul s-a schimbat. Un dezvoltator nu cumpără un slogan despre AI determinist. Un dezvoltator cumpără un format de graf în care identitatea poate fi verificată, stocată și folosită de unelte. Fișierul .keg nu este, așadar, o idee adăugată pe ultima sută de metri. Este o înregistrare portabilă a muncii.

Cele opt uși mici înainte ca o țintă să apară

When people imagine a compiler, they often jump straight to the backend. They picture the moment code becomes AVX, PTX, Verilog, or WASM. That moment matters, but it is late in the story. Most of the compiler work has already happened by then, in quiet stages that decide whether the backend can be trusted.

Backends arrive late. Most trust is earned earlier, in the small compiler stages that preserve meaning.

Kera begins with a UTF-8-aware lexer that converts source into tokens with full trivia tracking and error recovery. That sounds like editor plumbing, and it is. It is also toolchain reliability. If every byte position is preserved, diagnostics, formatting, incremental reparsing, and language server behavior can line up with what the user actually wrote.A compiler that loses the source shape early pays for that loss everywhere else.

The parser is event-based: recursive descent with Pratt expression parsing emits Start, Token, Finish, and Error events rather than building a single AST that everyone has toshare. The event stream feeds the tree builder, formatter, and language server independently. The green tree is lossless, preserving tokens and trivia. The graph builder then walks that tree, maintains name resolution, and creates a Region node per function.

Only after that does the source become the graph the compiler carries. Structural type checking validates compatibility across operation edges and tensor shape constraints. Types match only when their canonical hashes are identical. The borrow checker enforces one owner per value, no aliased mutable references, and ownership rules across memory spaces. Effect annotations are checked for consistency. Side effects are not left to taste or convention.

The optimisation pass manager then does the recognisable compiler work: constant folding, common subexpression elimination, dead code elimination, inlining, vectorisation, and loop fusion in a fixed-point loop. The important phrase is not the list of passes. It is what happens after every pass: a graph invariant validatorchecks hash consistency and dependency integrity. Optimisation is allowed to improve the graph, not make it mysterious.

Finally, target-specific code generators emit native code, PTX, Verilog, or WASM, and the optimised graph is serialized to a .keg file with a section table. The file starts with the magic bytes KEG\0 and carries sections for strings, types, attributes, nodes, regions, and exports. That is the path a team has to own before it can honestly talk about targets.

Targets are not stickers on aslide

A target list is easy to write and hard to deserve. CPU, GPU, FPGA, and WASM fit neatly in a product card. Lowering to them is where the invoice hides. Each target has its own habits, strengths, and failure modes. A compiler that wants deterministic heterogeneous execution cannot treat those habits as somebody else’s problem.

CPU, GPU, FPGA și WASM nu sunt etichete. Sunt lumi-țintă pe care același graf trebuie să le supraviețuiască.

Pe procesoarele CPU, Kera emite cod mașină nativ pentru x86-64 și ARM64, cu RISC-V Vector în fișa tehnică. Calea x86 selectează SSE2, AVX2 sau AVX-512; calea ARM folosește NEON; alocatorul de registre și planificatorul sunt partajate. Afirmația produsului nu este doar că există un binar. Afirmația este că nucleele SIMD specifice țintei sunt selectate în timp ce graful rămâne aceeași computație.

Pe procesoarele GPU, materialul sursă menționează generarea PTX pentru CUDA și ROCm, cu nuclee tensor utilizate atunci când forma nucleului o permite. Ultima clauză face treabă. Accelerarea hardware nu este praf magic. O formă de nucleu fie se potrivește unei căi de nuclee tensor, fie nu. Compilatorul trebuie să expună asta onest, să mute datele între gazdă și dispozitiv atunci când este nevoie și să păstreze amprenta grafului ca dovadă a ceea ce se execută.

Pe FPGA, calea de lowering devine o conversație hardware: sinteză Verilog, estimare a resurselor, analiză de timing și planificare a pipeline-ului pentru un comportament previzibil, exact la ciclu. Acesta este tipul de țintă unde vorbitul în vânt este deosebit de costisitor. Dacă cumpărătorul are nevoie de timp real strict, suprafața compilatorului trebuie să vorbească în cicluri, resurse și pipeline-uri, nu doar în viteză. Povestea Kera face din FPGA un backend al aceluiași graf, nu o rescriere de către o altă echipă.

Pe WASM, pagina menționează SIMD pe 128 de biți pentru implementări edge și browser. Asta contează pentru că edge este locul unde realitatea implementării se ciocnește adesea cu puritatea. Un browser, un dispozitiv mic sau un runtime constrâns nu pot găzdui întotdeauna același stack ca un server. O cale de lowering nativă pentru graf oferă echipei o modalitate de a duce aceeași computație în acel mediu fără a transforma edge-ul într-un al doilea produs.

Determinismul trebuie să supraviețuiască succesului

Determinismul este ușor de promis într-o cameră mică. Devine mai greu când produsul are succes. Vin mai mulți utilizatori. Apare mai mult hardware. Un model se mută de pe un cip pe altul. O linie de bază de test scrisă pentru un parc trebuie să acopere altul. O optimizare care pare inofensivă schimbă ordinea unei reduceri. O cale rapidă apare într-un runtime, dar nu în altul.

Determinismul este o buclă, nu un slogan: build, run, compare, fix și păstrează graful stabil.

Pagina Kera prezintă determinismul ca fiind identic bit cu bit pe toate platformele. Nu este o afirmație cosmetică. Schimbă obligațiile produsului. Aritmetica primitivă are semantică de precizie definită pe toate țintele. Reducerile au reguli definite de departajare, astfel încât planificarea și hardware-ul să nu schimbe rezultatul. Primitivelor rețelelor neuronale au implementări deterministe pe CPU, GPU, FPGA și WASM. Graful înseamnă același lucru oriunde rulează.

De aceea, comportamentul nedefinit, pauzele colectorului de gunoi și efectele secundare implicite nu sunt preferințe minore de limbaj. Sunt fisuri în suprafața de execuție. Pagina Kera spune fără dependență de LLVM, fără colector de gunoi, fără comportament nedefinit. Tipuri explicite, efecte explicite și proprietate peste spațiile de memorie sunt controale ale produsului. Ele reduc numărul de locuri în care un rezultat se poate schimba în timp ce toată lumea se uită în altă parte.

Există și o latură umană aici. Când un controler de robotică își schimbă comportamentul între dispozitive, când un flux de lucru financiar rotunjește un cent diferit, când un rezultat științific nu poate fi reprodus de un recenzent sau când o simulare de joc diverge între ținte, argumentul nu este de fapt despre teoria compilatoarelor. Este despre încrederea instituțională. Textul de marketing al Kera folosește povești simple pentru că problema de bază este simplu de simțit: aceleași instrucțiuni, același răspuns, pe orice computer deții.

Această încredere trebuie să supraviețuiască accelerării. Dacă performanța cere echipelor să abandoneze determinismul, produsul doar a mutat riscul. Kera încearcă să păstreze performanța și determinismul în același contract, făcând graful unitatea de semnificație și calea către țintă unitatea de execuție.

Securitatea face parte din lowering

Discuțiile despre compilatoare izolează adesea securitatea ca muncă de runtime. Materialul sursă al Kera nu face asta. Vorbește despre securitate bazată pe capabilități, un SecurityManager, o poartă de refuz implicit, un PolicyBuilder, seccomp BPF, namespace-uri Linux și un jurnal de audit rezistent la manipulare, sigur pentru fire de execuție, într-un buffer inelar cu capacitate fixă. Aceste cuvinte aparțin poveștii de inginerie pentru că lowering nu se termină când sunt emise instrucțiunile. Munca emisă trebuie totuși să ruleze cu granițe.

Tokenurile de capabilitate sunt acordate la pornire. Acțiunile privilegiate necesită o capabilitate explicită. Regulile de acces sunt declarate dinainte. Un filtru BPF restricționează apelurile de sistem. Izolarea prin namespace separă vizualizările de proces, montare și rețea. Evenimentele relevante pentru securitate ajung într-un jurnal de audit. Pe scurt: nimic nu este permis implicit.

Asta contează pentru execuția eterogenă pentru că țintele creează suprafețe. Un proces pe CPU, un transfer pe GPU, o cale pe FPGA, un runtime de browser și un job distribuit nu eșuează în moduri identice. Un produs trebuie să țină politica atașată muncii pe măsură ce aceasta se mișcă. Dacă graful spune ce este calculul, controalele runtime-ului spun ce poate face calculul.

Există și un motiv operațional pentru a aduce securitatea în povestea compilatorului. Echipele nu vor un produs pentru compilare, altul pentru politică, altul pentru logare și altul pentru explicații dacă cusăturile dintre ele devin locul unde se ascund incidentele. Pagina Kera nu pretinde că rezolvă toată securitatea. Face ceva mai restrâns și mai util: face permisiunile explicite și auditate în mediul de execuție pe care compilatorul îl alimentează.

Munca distribuită este tot lowering

Fișa tehnică menționează execuția distribuită, ring allreduce și recuperarea din checkpoint. În alt produs, acestea ar putea fi elemente de broșură. În Kera, ele aparțin aceleiași conversații despre lowering pentru că paralelismul schimbă forma unui calcul. Paralelismul de date, de model și de pipeline nu sunt doar moduri de a merge mai repede. Sunt moduri de a împărți munca fără a pierde semnificația grafului original.

Dacă un job mare rulează pe multe mașini și un nod cedează, recuperarea dintr-un checkpoint nu este o facilitate. Face parte din procesul de a face calculul operațional. Dacă rezultatele sunt agregate în mod fiabil, semantica de agregare trebuie definită. Dacă un graf este adresat prin conținut, calea distribuită trebuie să păstreze identitatea grafului, nu să inventeze o a doua realitate odată ce jobul părăsește o mașină.

Aici interpretarea de produs devine clară. Kera nu este un strat de sintaxă cu un add-on distribuit. Încearcă să facă calculul portabil atât ca formă, cât și ca hardware: un singur program, un singur graf, mai multe ținte de execuție și o înregistrare care poate fi verificată. Execuția distribuită este un alt loc în care lowering-ul trebuie să transporte contractul de execuție.

Aceasta schimbă și povestea despre personal. Fără o cale comună de compilare, un bug care apare doar pe o platformă poate necesita oameni care cunosc simultan cipul, toolchain-ul și runtime-ul. Cu un singur graf și o cale comună, întrebarea este mai clară: s-a schimbat graful, s-a schimbat calea de lowering sau ținta a încălcat semantica definită? Întrebările mai bune nu elimină munca grea. Ele opresc munca grea să se răspândească aleatoriu.

De ce cineva trebuie să dețină calea

Lowering-ul nu poate rămâne fără proprietar odată ce transportă rezultate de care oamenii depind. Un graf care rulează pe CPU, GPU, FPGA și WASM are nevoie de mai mult decât o reprezentare ingenioasă. Are nevoie de documentație, diagnostice, afirmații despre ținte, comportament al editorului, generare de cod, controale de securitate, conversații de suport și o modalitate de a spune unui cumpărător exact ce s-a schimbat atunci când un rezultat se schimbă.

Aceasta nu face munca mai puțin tehnică. Face munca tehnică mai obligatorie. Kera este în prezent un produs comercial Dweve, nu o versiune open-source: toate drepturile rezervate, disponibil sub licență comercială, fabricat în Țările de Jos și destinat organizațiilor care au nevoie ca calea grafului să fie suportată, nu doar publicată. Dacă pagina menționează Rust 2021, IR de graf, JIT personalizat, fără LLVM, CPU/GPU/FPGA/WASM, urmărirea efectelor, ownership peste spații de memorie, securitate prin capabilități, execuție distribuită, integrare C și Python, fluxuri de lucru CLI, suport pentru editor și instrumente LSP, acestea nu sunt detalii interne decorative. Sunt obligații care trebuie să supraviețuiască evaluării prin sarcini de lucru reale.

Fișa de capabilități este atentă în acest sens. Descrie ținte de proiectare și spune să verifici în raport cu sarcina ta de lucru. Această reținere contează. Este mult mai sănătoasă decât să pretinzi că fiecare cifră de benchmark se transferă. Sistemul ar trebui să ofere echipelor o modalitate de a măsura, inspecta și compara în propriul mediu, nu să le ceară să accepte o poveste universală despre viteză.

Cineva trebuie să dețină acea cale pentru că fiecare țintă încearcă să facă sursa mai puțin universală. Kera este locul unde acele diferențe de țintă devin muncă explicită de compilator, nu folclor transmis după raportul de bug.

Ziua în care graful devine contractul

Imaginează-ți din nou raportul original de bug, dar cu Kera deja în fluxul de lucru. O echipă financiară aprobă un calcul de risc. Amprenta grafului este înregistrată. Prima implementare rulează pe CPU. Mai târziu, o cale GPU este introdusă pentru analiza portofoliului. Și mai târziu, o cale FPGA este folosită pentru prețuri cu latență mai mică. Întrebarea la fiecare pas nu este dacă noul hardware sună impresionant. Întrebarea este dacă același graf este lowered și dacă semantica țintei menține rezultatul identic.

Conversația se schimbă. Echipa de platformă poate vorbi despre programare și costuri. Echipa de compilatoare poate vorbi despre backend-uri. Echipa de conformitate poate vorbi despre amprentă. Proprietarul afacerii poate întreba dacă mutarea hardware-ului este o decizie operațională sau o rescriere. Produsul le oferă tuturor un singur obiect la care să se raporteze.

Acesta este meritul tăcut al unui IR adresabil prin conținut. Transformă execuția cross-platform dintr-o serie de explicații persuasive într-o înregistrare. Face din graf o chitanță. Permite aceluiași fișier .keg să alimenteze fiecare backend. Oferă organizației o bază de referință care aparține calculului, nu unei singure mașini.

Niciun compilator nu poate elimina nevoia de judecată inginerească. Echipele trebuie totuși să selecteze cu atenție țintele, să testeze onest sarcinile de lucru, să înțeleagă limitele țintelor și să decidă ce domenii cer identitate bit-cu-bit. Dar Kera poate face aceste decizii explicite. Poate împiedica o migrare de performanță să devină, din greșeală, o migrare semantică.

Lecția lowering-ului

Kera contează pentru că partea grea nu este să ai o idee ingenioasă de lowering. Partea grea este să duci acea idee prin toate locurile plictisitoare în care ingineria fie devine demnă de încredere, fie devine folclor: diagnosticare, arbori verzi, hash-uri de tipuri, verificarea împrumuturilor, optimizare în punct fix, validarea invariantelor, serializarea .keg, emisia backend, politica de securitate, jurnalele de audit, suportul pentru editor și execuția specifică țintei.

Lowering-ul este muncă pentru că fiecare țintă încearcă să facă sursa mai puțin universală. Ingineria compilatoarelor deterministe este disciplina de a refuza să lași asta să se întâmple în tăcere. CPU-ul vrea vectori. GPU-ul vrea kernel-uri. FPGA-ul vrea cicluri. WASM-ul vrea constrângeri. Organizația vrea un singur răspuns. Treaba lui Kera este să păstreze sensul calculului, lăsând fiecare țintă să facă ceea ce știe mai bine.

Aceasta este o formă inginerească, nu o abstracție de hârtie. Are un cumpărător, un mod de eșec, un format de fișier, o cale de compilator și consecințe operaționale. Promisiunea nu este că hardware-ul devine simplu. Promisiunea este că complexitatea este reprezentată, redusă, verificată și asumată.

Când apare următorul număr aproape corect, echipa nu ar trebui să înceapă cu folclor despre ce mașină a rulat ce. Ar trebui să înceapă cu graful. S-a potrivit amprenta? Care backend a emis codul? Ce capabilități au fost acordate? Ce semantică de țintă s-a aplicat? Ce artefact a fost stocat? Acestea sunt întrebări de produs. Kera există pentru că sunt și întrebări de compilator.