Kera og arbejdet med at sænke

Det kompilatorhistorie om det uglamourøse arbejde mellem en ren graf og en rigtig maskine: deterministisk lowering, target-ejerskab og beviser, der holder...

Kera og arbejdet med at sænke

Fejlrapporten, der ikke var en fejl

Historien begynder som regel med et tal, der er næsten rigtigt. Ikke vildt forkert. Ikke i stykker på en måde, der gør dashboardet rødt. Næsten rigtigt på den dyre måde: den slags forkert, der får ét team til at frigive, et andet team til at tøve, og en revisor til at spørge, hvorfor svaret ændrede sig, da den samme model flyttede fra én maskine til en anden.

En build kører på en x86-server og producerer en værdi. Den næste build kører på en ARM-laptop og producerer en værdi, der er tæt nok på, at en demo overlever. En GPU-sti er hurtigere, men runder gennem beregningen anderledes. En FPGA-sti er attraktiv for timing, men pludselig har softwareteamet en hardware-samtale. Alle kan forklare en lille del af forskellen. Ingen ejer hele stien fra kilde til resultat.

Det er det rum, Kera er bygget til. Ikke modelideen, ikke marketingsliden, ikke et papir om en fremtidig compiler, men compilerarbejdet, der begynder, når et team siger, at den samme beregning skal køre på forskellige maskiner og stadig betyde det samme. Den forpligtelse har brug for en ejer. Compilerløftet er ikke, at lowering er elegant. Løftet er, at lowering behandles som arbejde.

Lowering er den del af en compiler, hvor pæn sprogintention bliver til instruktioner, som en target kan udføre. Det er også her, vage påstande bliver synlige. Hvis resultatet skal være bit for bit identisk på tværs af CPU, GPU, FPGA og WebAssembly, så kan compileren ikke bare trække på skuldrene over target-forskelle. Den skal bære nok struktur til at beslutte, hvad der må ændre sig, og hvad der ikke må ændre sig. Den skal kende hukommelsesrum, effekter, opkoder, dataflytning, target-begrænsninger og kvitteringerne, der beviser, at to builds er den samme beregning.

Den fristende version af historien er at kalde det et portabilitetslag. Det er for småt. Portabilitet siger, at programmet kører andre steder. Kera sigter mod den hårdere udtalelse: programmet er repræsenteret som en content-addressed graf, kompileret til en .keg-artefakt, lowered til flere backends og producerer stadig det samme svar. Køkkenet skifter. Opskriften gør ikke.

Mellemrummet, ingen vil sælge

Den første samtale med en seriøs køber begynder sjældent med syntaks. Den begynder med et rod. Der er en model, der betyder noget. Der er en simulation, der plejede at være forskning og er blevet operationel. Der er en risikoberegning, hvis resultat ikke længere må variere fra maskine til maskine. Der er en edge-deployment, der ikke kan bære hele cloud-runtime. Der er et team, der vil have en accelerator, men ikke har råd til en omskrivning, hver gang acceleratoren ændrer sig.

Det meste værktøj får det til at ligne et deploymentsproblem. Vælg target, eksportér modellen, patch runtime, accepter noget varians, og skriv så et dokument, der forklarer undtagelserne. Dokumentet vokser. Testmatricen vokser. Antallet af specialister vokser. Til sidst betaler organisationen for heterogenitet to gange: én gang, når den køber hardwaren, og igen, når den forsøger at bevise, at hardwaren lavede det samme arbejde.

Kera starter fra den modsatte ende. Kera-siden kalder det et statisk typet systemprogrammeringssprog med en graph-native, content-addressed IR. Den sætning betyder noget, fordi grafen ikke er et dekorativt compilerdiagram. Det er det eksekverbare objekt. Kildekode lowers til rettede acykliske grafer af operationer, gemt som .keg-filer. Hver node bærer defineret struktur. Duplikeret arbejde kan elimineres strukturelt. Den samme graf kan lowers til CPU, GPU, FPGA og WASM uden at behandle hver target som et separat lille univers.

Derfor skal Kera opføre sig som seriøs compiler-infrastruktur frem for et diagram. Det ubehagelige mellemrum er, hvor kunden lever. Tokenisering skal bevare byte-positioner. Parsing skal fodre værktøjer såvel som kompilering. Typechecking skal afvise form- og kantmismatch, før de bliver runtime-hændelser. Borrow checking skal forstå ejerskab på tværs af host-, device-, pinned- og unified-hukommelse. Optimering skal være reproducerbar. Serialisering skal give organisationen et artefakt, den kan beholde.

Intet af det giver en dramatisk overskrift. Det er langt mere nyttigt end en overskrift. Det betyder, at når en arbejdsbyrde flytter fra udvikling til produktion, eller fra en CPU-baseline til en GPU-sti, har compileren en stabil ting at sammenligne med: graffingeraftrykket. Hvis fingeraftrykket matcher, er arbejdet det samme arbejde. Det er et fælles kontrolpunkt, som en ingeniør, en køber og en revisor alle kan forstå.

En graf er en kvittering, ikke et billede

Den gamle måde at forklare et program på er at vise kilden og bede læseren om at stole på compileren. Kera-siden vender hele tiden tilbage til et andet objekt: grafen. Hver operation er en node. Kanter udtrykker afhængigheder. Grafen har indhold. Indholdet har en hash. Hashen bliver til et fingeraftryk, der følger med gennem bygningen.

Grafen er det holdbare objekt: kilde, typer, regioner, hashes og det artefakt, der følger med.

Det lyder som intern maskineri, indtil man står ved siden af et compliance-team. Et compliance-team vil ikke vide, at en leverandør har en pæn compiler-arkitektur. Det vil vide, om beregningen, der blev godkendt sidste måned, er den beregning, der kører i dag. Graffingeraftrykket er broen mellem de to verdener. Det er ikke et skærmbillede af compileren. Det er en kompakt måde at sige: dette operationssæt, disse typer, disse input, disse attributter, denne afhængighedsstruktur.

Node-modellen er bevidst streng. En node er en skemaversion, en opcode, output-deskriptorer, input-hashes og attributkort lagt ud i kanonisk rækkefølge. Bytene hashes med SHA3-256 under domæneadskilte præfikser, så node-, region- og graf-hashes lever i separate navnerum. Hvis to noder har samme opcode, input og attributter, har de samme hash. Indsættelse af en dublet returnerer den eksisterende post. Common subexpression elimination bliver en konsekvens af repræsentationen, ikke en heroisk pass, der tilføjes senere.

Det er den slags detaljer, der forvandler et ingeniørsystem til noget, folk kan stole på. En køber køber ikke et compiler-diagram. En køber køber færre argumenter om, hvorvidt en beregning er ændret. En udvikler køber ikke et slogan om deterministisk AI. En udvikler køber et graf-format, hvor lighed kan kontrolleres, gemmes og bruges af værktøjer. .keg-filen er derfor ikke en implementerings-tanketorsk. Den er en bærbar registrering af arbejdet.

De otte små døre, før et mål dukker op

Når folk forestiller sig en compiler, springer de ofte direkte til backend. De ser for sig det øjeblik, hvor kode bliver til AVX, PTX, Verilog eller WASM. Det øjeblik betyder noget, men det er sent i historien. Det meste af compilerarbejdet er allerede sket på det tidspunkt, i stille faser, der afgør, om backend kan betros.

Backends kommer sent ind. Det meste af tilliden er optjent tidligere, i de små compilerfaser, der bevarer betydningen.

Kera begynder med en UTF-8-bevidst lexer, der konverterer kildekode til tokens med fuld trivia-sporing og fejlgendannelse. Det lyder som editorinfrastruktur, og det er det også. Det er også værktøjskædens pålidelighed. Hvis hver byteposition bevares, kan diagnostik, formatering, inkrementel genparsning og sprogserveradfærd stemme overens med det, brugeren faktisk skrev. En compiler, der mister kildekodens form tidligt, betaler for det tab alle andre steder.

Parseren er hændelsesbaseret: rekursiv nedstigning med Pratt-udtryksparsning udsender Start-, Token-, Finish- og Error-hændelser i stedet for at bygge et enkelt AST, som alle skal dele. Hændelsesstrømmen fodrer træbyggeren, formateringen og sprogserveren uafhængigt. Det grønne træ er tabsfrit og bevarer tokens og trivia. Grafbyggeren gennemgår derefter træet, vedligeholder navneopløsning og opretter en Region-node pr. funktion.

Først derefter bliver kildekoden til den graf, compileren bærer rundt på. Strukturel typekontrol validerer kompatibilitet på tværs af operationskanter og tensorformbegrænsninger. Typer matcher kun, når deres kanoniske hashes er identiske. Borrow-checkeren håndhæver én ejer pr. værdi, ingen aliasede muterbare referencer og ejerskabsregler på tværs af hukommelsesrum. Effektannotationer kontrolleres for konsistens. Sideeffekter overlades ikke til smag eller konvention.

Optimiserings-pass-manageren udfører derefter det genkendelige compilerarbejde: konstantfoldning, eliminering af fælles deludtryk, eliminering af død kode, inlining, vektorisering og loop-fusion i en fikspunktssløjfe. Det vigtige udtryk er ikke listen over passes. Det er det, der sker efter hver pass: en grafinvariantvalidator kontrollerer hash-konsistens og afhængighedsintegritet. Optimering må forbedre grafen, ikke gøre den uigennemskuelig.

Til sidst udsender targetspecifikke kodegeneratorer native kode, PTX, Verilog eller WASM, og den optimerede graf serialiseres til en .keg-fil med en sektionstabel. Filen starter med magiske bytes KEG\0 og indeholder sektioner for strenge, typer, attributter, noder, regioner og eksporter. Det er den vej, et team skal eje, før det ærligt kan tale om targets.

Targets er ikke klistermærker på en slide

En targetliste er let at skrive og svær at fortjene. CPU, GPU, FPGA og WASM passer pænt ind i et produktkort. Nedkobling til dem er, hvor regningen gemmer sig. Hver target har sine egne vaner, styrker og fejltilstande. En compiler, der ønsker deterministisk heterogen eksekvering, kan ikke behandle de vaner som en andens problem.

CPU, GPU, FPGA og WASM er ikke etiketter. De er target-verdener, som den samme graf skal overleve i.

På CPU’er udsender Kera native maskinkode til x86-64 og ARM64, med RISC-V Vector i det tekniske datablad. x86-stien vælger SSE2, AVX2 eller AVX-512; ARM-stien bruger NEON; registerallokatoren og scheduleren er fælles. Produktpåstanden er ikke blot, at der findes en binær. Påstanden er, at targetspecifikke SIMD-kerner vælges, mens grafen forbliver den samme beregning.

På GPU’er nævner kildematerialet PTX-generering til CUDA og ROCm, med tensor-kerner, når kerneformen tillader det. Den sidste klausul gør et stykke arbejde. Hardwareacceleration er ikke magisk støv. En kerneform passer enten til en tensor-kerne-sti, eller også gør den ikke. Compileren skal ærligt eksponere det, flytte data mellem vært og enhed, når det er nødvendigt, og stadig beholde grafens fingeraftryk som bevis på, hvad der udføres.

På FPGA bliver lowering-stien en hardware-samtale: Verilog-syntese, ressourceestimering, timing-analyse og pipeline-planlægning til forudsigelig, cyklus-eksakt adfærd. Det er den slags target, hvor håndbølgen er særligt dyr. Hvis køberen har brug for hård realtid, skal compilerens overflade tale i cyklusser, ressourcer og pipelines, ikke kun hastighed. Keras fortælling gør FPGA til en backend for den samme graf i stedet for en omskrivning af et andet team.

På WASM nævner siden 128-bit SIMD til edge- og browserudrulning. Det betyder noget, fordi edge er, hvor udrulningsvirkeligheden ofte kolliderer med renheden. En browser, en lille enhed eller en begrænset runtime kan ikke altid være vært for den samme stak som en server. En graf-native lowering-sti giver teamet en måde at bære den samme beregning ind i det miljø uden at gøre edge til et andet produkt.

Determinisme skal overleve succes

Determinisme er let at love i et lille rum. Det bliver sværere, når produktet får succes. Flere brugere kommer til. Mere hardware dukker op. En model flytter fra en chip til en anden. En testbaseline skrevet til én ejendom skal dække en anden. En optimering, der ser harmløs ud, ændrer rækkefølgen af en reduktion. En hurtig sti dukker op i én runtime, men ikke i en anden.

Determinisme er en løkke, ikke et slogan: byg, kør, sammenlign, ret, og hold grafen stabil.

Kera-siden beskriver determinisme som bit for bit på tværs af platforme. Det er ikke en kosmetisk påstand. Det ændrer produktforpligtelser. Primitive aritmetiske operationer har defineret præcisionssemantik på alle målsystemer. Reduktioner har defineret tie-breaking, så planlægning og hardware ikke ændrer resultatet. Primitiver til neurale netværk har deterministiske implementeringer på CPU, GPU, FPGA og WASM. Grafen betyder det samme, uanset hvor den kører.

Det er derfor, at udefineret adfærd, garbage collector-pauser og implicitte sideeffekter ikke er små sprogpræferencer. De er revner i eksekveringsfladen. Kera-siden siger ingen LLVM-afhængighed, ingen garbage collector, ingen udefineret adfærd. Eksplicitte typer, eksplicitte effekter og ejerskab på tværs af hukommelsesrum er produktkontroller. De reducerer antallet af steder, hvor et resultat kan ændre sig, mens alle kigger et andet sted hen.

Der er også en menneskelig side til dette. Når en robotstyring ændrer adfærd på tværs af enheder, når en finansiel arbejdsgang runder en cent anderledes, når et videnskabeligt resultat ikke kan gentages af en reviewer, eller når en spilsimulation divergerer mellem målsystemer, handler argumentet ikke rigtig om kompilatorteori. Det handler om institutionel tillid. Keras forbrugertekst bruger enkle historier, fordi det underliggende problem er enkelt at mærke: samme instruktioner, samme svar, på enhver computer, du ejer.

Den tillid skal overleve acceleration. Hvis performance kræver, at teams opgiver determinisme, har produktet kun flyttet risikoen. Kera forsøger at holde performance og determinisme i samme kontrakt ved at gøre grafen til enheden for betydning og målstien til enheden for eksekvering.

Sikkerhed er en del af lowering

Kompilatordiskussioner isolerer ofte sikkerhed som runtime-arbejde. Keras kildemateriale gør ikke det. Det taler om kapabilitetsbaseret sikkerhed, en SecurityManager, en default-deny-port, en PolicyBuilder, seccomp BPF, Linux-navnerum og en trådsikker manipulationssikker revisionslog i en ringbuffer med fast kapacitet. De ord hører til i ingeniørhistorien, fordi lowering ikke slutter, når instruktioner er genereret. Det genererede arbejde skal stadig køre med grænser.

Kapabilitetstokens tildeles ved opstart. Privilegerede handlinger kræver en eksplicit kapabilitet. Adgangsregler erklæres på forhånd. Et BPF-filter begrænser syscalls. Navnerumsisolering adskiller proces-, mount- og netværksvisninger. Sikkerhedsrelevante hændelser lander i en revisionslog. Den korte sætning er: intet er tilladt som standard.

Det betyder noget for heterogen eksekvering, fordi målsystemer skaber overflader. En CPU-proces, GPU-overførsel, FPGA-sti, browserruntime og distribueret job fejler ikke på identiske måder. Et produkt skal holde politikken knyttet til arbejdet, når det bevæger sig. Hvis grafen siger, hvad beregningen er, siger runtime-kontrollerne, hvad beregningen må gøre.

Der er også en driftsmæssig grund til at bringe sikkerhed ind i kompilatorhistorien. Teams ønsker ikke ét produkt til kompilering, et andet til politik, et andet til logning og et andet til forklaring, hvis sømmene mellem dem bliver det sted, hvor hændelser gemmer sig. Kera-siden hævder ikke at løse al sikkerhed. Den gør noget smallere og mere nyttigt: den gør tilladelser eksplicitte og reviderede i det eksekveringsmiljø, som compileren fodrer.

Distribueret arbejde er stadig lowering

Det tekniske datablad nævner distribueret eksekvering, ring allreduce og checkpoint-gendannelse. I et andet produkt kunne det være brochurepunkter. I Kera hører de til i den samme lowering-samtale, fordi parallelisme ændrer formen på en beregning. Data-, model- og pipeline-parallelisme er ikke bare måder at gå hurtigere på. De er måder at dele arbejde op uden at miste betydningen af den oprindelige graf.

Hvis et stort job kører på tværs af mange maskiner, og en node fejler, er genoprettelse fra et checkpoint ikke en bekvemmelighed. Det er en del af at gøre beregningen operationel. Hvis resultater aggregeres pålideligt, skal aggregeringssemantikken defineres. Hvis en graf er content-addressed, skal den distribuerede sti bevare grafens identitet i stedet for at opfinde en anden virkelighed, når jobbet forlader én maskine.

Det er her, produktfortolkningen bliver klar. Kera er ikke et syntakslag med et distribueret tilføjelsesmodul. Den forsøger at gøre beregningen bærbar på tværs af både form og hardware: ét program, én graf, flere eksekveringsmål og en registrering, der kan kontrolleres. Distribueret eksekvering er endnu et sted, hvor lowering skal bære eksekveringskontrakten.

Det ændrer også bemandingshistorien. Uden en fælles kompileringssti kan en fejl, der kun vises på én platform, kræve folk, der kender chippen, værktøjskæden og runtime på samme tid. Med én graf og en fælles sti bliver spørgsmålet skarpere: ændrede grafen sig, ændrede lowering-stien sig, eller overtrådte målet den definerede semantik? Bedre spørgsmål fjerner ikke hårdt arbejde. De forhindrer, at det hårde arbejde spreder sig tilfældigt.

Hvorfor nogen skal eje stien

Lowering kan ikke forblive ejerløs, når den bærer resultater, folk er afhængige af. En graf, der kører på CPU, GPU, FPGA og WASM, har brug for mere end en smart repræsentation. Den har brug for dokumentation, diagnostik, målkrav, editoradfærd, kodegenerering, sikkerhedskontroller, supportdialoger og en måde at fortælle en køber præcis, hvad der ændrede sig, når et resultat ændres.

Det gør ikke arbejdet mindre teknisk. Det gør det tekniske arbejde mere forpligtende. Kera er i øjeblikket et kommercielt Dweve-produkt, ikke en open source-udgivelse: alle rettigheder forbeholdt, tilgængelig under kommerciel licens, lavet i Holland og beregnet til organisationer, der har brug for, at grafstien understøttes snarere end blot offentliggøres. Hvis siden siger Rust 2021, graf-IR, custom JIT, ingen LLVM, CPU/GPU/FPGA/WASM, effektsporing, ejerskab på tværs af hukommelsesrum, capability-sikkerhed, distribueret eksekvering, C- og Python-embedding, CLI-workflows, editorunderstøttelse og LSP-værktøjer, er det ikke dekorative interne detaljer. Det er forpligtelser, der skal overleve evaluering med rigtige workloads.

Kapabilitetsarket er omhyggeligt med dette. Det beskriver designmål og siger, at man skal verificere mod sin egen workload. Den tilbageholdenhed betyder noget. Det er langt sundere end at lade som om, at hvert benchmarktal kan overføres. Systemet skal give teams en måde at måle, inspicere og sammenligne i deres eget miljø, ikke bede dem om at acceptere en universel hastighedshistorie.

Nogen skal eje den sti, fordi hvert mål forsøger at gøre kilden mindre universel. Kera er stedet, hvor disse målforskelle bliver eksplicit kompileringsarbejde i stedet for folklore, der videregives efter fejlrapporten.

Dagen, hvor grafen bliver kontrakten

Forestil dig den oprindelige fejlrapport igen, men med Kera allerede i workflowet. Et finanshold godkender en risikoberegning. Graffingeraftrykket registreres. Den første implementering kører på CPU. Senere introduceres en GPU-sti til porteføljeanalyse. Senere igen bruges en FPGA-sti til prisfastsættelse med lavere latenstid. Spørgsmålet på hvert trin er ikke, om den nye hardware lyder imponerende. Spørgsmålet er, om den samme graf bliver lowered, og om målsemantikken holder resultatet identisk.

Samtalen ændrer sig. Platformteamet kan tale om planlægning og omkostninger. Kompileringsteamet kan tale om backends. Compliance-teamet kan tale om fingeraftrykket. Forretningsejeren kan spørge, om det at flytte hardware er en operationel beslutning eller en omskrivning. Produktet giver dem alle ét objekt at pege på.

Det er den stille værdi ved et indholdsadresseret IR. Det forvandler eksekvering på tværs af platforme fra en række overbevisende forklaringer til en registrering. Det gør grafen til en kvittering. Det lader den samme .keg fodre alle backends. Det giver organisationen en baseline, der tilhører beregningen frem for en enkelt maskine.

Ingen compiler kan fjerne behovet for ingeniørmæssig dømmekraft. Teams skal stadig vælge mål omhyggeligt, teste workloads ærligt, forstå målbegrænsninger og beslutte, hvilke domæner der kræver bit-for-bit-identitet. Men Kera kan gøre disse beslutninger eksplicitte. Det kan forhindre, at en ydelsesmigrering ved et uheld bliver til en semantisk migrering.

Lektien om lowering

Kera betyder noget, fordi den svære del ikke er at have en smart lowering-idé. Den svære del er at føre den idé igennem alle de kedelige steder, hvor ingeniørarbejde enten bliver pålideligt eller bliver til folklore: diagnostik, grønne træer, type-hashes, borrow checking, fixed-point-optimering, invariantvalidering, .keg-serialisering, backend-emission, sikkerhedspolitik, revisionslogge, editorunderstøttelse og målspecifik eksekvering.

Lowering er arbejde, fordi hvert mål forsøger at gøre kilden mindre universel. Deterministisk compiler-engineering er disciplinen, der nægter at lade det ske i stilhed. CPU vil have vektorer. GPU vil have kernels. FPGA vil have cyklusser. WASM vil have begrænsninger. Organisationen vil have ét svar. Keras opgave er at bevare beregningens betydning, mens hvert mål får lov til at gøre det, det er godt til.

Det er en ingeniørmæssig form, ikke en papirabstraktion. Den har en køber, en fejltilstand, et filformat, en compiler-sti og operationelle konsekvenser. Løftet er ikke, at hardware bliver enkel. Løftet er, at kompleksiteten er repræsenteret, lowered, kontrolleret og ejet.

Når det næste næsten-rigtige tal ankommer, skal teamet ikke skulle starte med folklore om, hvilken maskine der kørte hvad. Det skal starte med grafen. Matchede fingeraftrykket? Hvilken backend emitterede koden? Hvilke kapaciteter blev tildelt? Hvilke mål-semantikker blev anvendt? Hvilken artefakt blev gemt? Det er produktspørgsmål. Kera findes, fordi de også er compiler-spørgsmål.