Kera e il lavoro di riduzione
Il bug report che non era un bug
La storia di solito inizia con un numero quasi giusto. Non clamorosamente sbagliato. Non rotto in un modo che fa diventare rossa la dashboard. Quasi giusto nel modo costoso: il tipo di errore che permette a un team di rilasciare, a un altro di esitare, e a un revisore di chiedersi perché la risposta è cambiata quando lo stesso modello è passato da una macchina all'altra.
Una build gira su un server x86 e produce un valore. La build successiva gira su un laptop ARM e produce un valore abbastanza vicino da far sopravvivere una demo. Un percorso GPU è più veloce ma arrotonda in modo diverso durante il calcolo. Un percorso FPGA è interessante per i tempi, ma all'improvviso il team software si ritrova a fare una conversazione hardware. Tutti sanno spiegare un piccolo pezzo della differenza. Nessuno possiede l'intero percorso dalla sorgente al risultato.
È questo lo spazio per cui Kera è pensato. Non l'idea del modello, non la slide di marketing, non un paper su un compilatore futuro, ma il lavoro di compilazione che inizia quando un team dice che la stessa computazione deve girare su macchine diverse e significare comunque la stessa cosa. Questa responsabilità ha bisogno di un proprietario. La promessa del compilatore non è che l'abbassamento sia elegante. La promessa è che l'abbassamento sia trattato come lavoro.
L'abbassamento è la parte del compilatore in cui le buone intenzioni del linguaggio diventano istruzioni che un target può eseguire. È anche il punto in cui le affermazioni vaghe diventano visibili. Se il risultato deve essere identico bit per bit su CPU, GPU, FPGA e WebAssembly, allora il compilatore non può ignorare le differenze tra i target. Deve portare con sé abbastanza struttura per decidere cosa può cambiare e cosa non deve cambiare. Deve conoscere spazi di memoria, effetti, opcode, movimento dei dati, limiti del target e le ricevute che dimostrano che due build sono la stessa computazione.
La versione allettante della storia è chiamarla un livello di portabilità. È troppo poco. La portabilità dice che il programma gira altrove. Kera punta all'affermazione più difficile: il programma è rappresentato come un grafo content-addressed, compilato in un artefatto .keg, abbassato su più backend, e produce comunque la stessa risposta. La cucina cambia. La ricetta no.
Il punto di mezzo che nessuno vuole vendere
La prima conversazione con un acquirente serio raramente inizia dalla sintassi. Inizia da un pasticcio. C'è un modello che conta. C'è una simulazione che una volta era ricerca ed è diventata operativa. C'è un calcolo di rischio il cui risultato non può più variare da macchina a macchina. C'è un deployment edge che non può portare l'intero runtime cloud. C'è un team che vuole un acceleratore ma non può permettersi una riscrittura ogni volta che l'acceleratore cambia.
La maggior parte degli strumenti fa sembrare questo un problema di deployment. Scegli il target, esporta il modello, applica una patch al runtime, accetta una certa varianza, poi scrivi un documento che spiega le eccezioni. Il documento cresce. La matrice di test cresce. Il numero di specialisti cresce. Alla fine l'organizzazione paga due volte per l'eterogeneità: una volta quando compra l'hardware, un'altra quando cerca di dimostrare che l'hardware ha fatto lo stesso lavoro.
Kera parte dall'estremità opposta. La pagina di Kera lo chiama un linguaggio di sistemi staticamente tipizzato con un IR graph-native e content-addressed. Questa frase conta perché il grafo non è un diagramma decorativo del compilatore. È l'oggetto eseguibile. Il codice sorgente si abbassa in grafi aciclici diretti di operazioni, memorizzati come file .keg. Ogni nodo porta una struttura definita. Il lavoro duplicato può essere eliminato strutturalmente. Lo stesso grafo può essere abbassato su CPU, GPU, FPGA e WASM senza trattare ogni target come un piccolo universo separato.
Ecco perché Kera deve comportarsi come una seria infrastruttura di compilazione, non come un diagramma. È in quella scomoda via di mezzo che vive il cliente. La tokenizzazione deve preservare le posizioni dei byte. Il parsing deve alimentare sia gli strumenti di sviluppo sia la compilazione. Il type checking deve rifiutare le discrepanze di forma e di bordi prima che diventino eventi a runtime. Il borrow checking deve comprendere la proprietà attraverso memoria host, device, pinned e unificata. L'ottimizzazione deve essere ripetibile. La serializzazione deve dare all'organizzazione un artefatto che possa conservare.
Niente di tutto ciò fa un titolo eclatante. Ed è molto più utile di un titolo. Significa che quando un carico di lavoro passa dallo sviluppo alla produzione, o da una baseline CPU a un percorso GPU, il compilatore ha una cosa stabile da confrontare: l'impronta del grafo. Se l'impronta corrisponde, il lavoro è lo stesso lavoro. È un punto di controllo condiviso che un ingegnere, un acquirente e un revisore possono tutti comprendere.
Un grafo è una ricevuta, non un'immagine
Il vecchio modo di spiegare un programma è mostrare il sorgente e chiedere al lettore di fidarsi del compilatore. La pagina di Kera continua a tornare su un oggetto diverso: il grafo. Ogni operazione è un nodo. I bordi esprimono le dipendenze. Il grafo ha un contenuto. Il contenuto ha un hash. L'hash diventa un'impronta che viaggia attraverso la build.
Sembra meccanica interna finché non ti trovi accanto a un team di conformità. Un team di conformità non vuole sapere che un fornitore ha una bella architettura di compilatore. Vuole sapere se il calcolo approvato il mese scorso è il calcolo in esecuzione oggi. L'impronta del grafo è il ponte tra questi due mondi. Non è uno screenshot del compilatore. È un modo compatto per dire: questo insieme di operazioni, questi tipi, questi input, questi attributi, questa struttura di dipendenze.
Il modello dei nodi è volutamente rigoroso. Un nodo è una versione di schema, un opcode, descrittori di output, hash di input e mappe di attributi disposti in ordine canonico. I byte vengono sottoposti a hash con SHA3-256 sotto prefissi separati per dominio, così gli hash di nodo, regione e grafo vivono in namespace separati. Se due nodi hanno lo stesso opcode, gli stessi input e gli stessi attributi, hanno lo stesso hash. L'inserimento di un duplicato restituisce la voce esistente. L'eliminazione di sottoespressioni comuni diventa una conseguenza della rappresentazione, non una pass eroica aggiunta in seguito.
È questo genere di dettaglio che trasforma un sistema ingegneristico in qualcosa su cui le persone possono fare affidamento. Un acquirente non sta comprando un diagramma del compilatore. Un acquirente sta comprando meno discussioni sul fatto che un calcolo sia cambiato. Uno sviluppatore non sta comprando uno slogan sull'IA deterministica. Uno sviluppatore sta comprando un formato di grafo in cui l'uguaglianza può essere verificata, archiviata e usata dagli strumenti. Il file .keg non è quindi un ripensamento implementativo. È una registrazione portabile del lavoro.
Le otto piccole porte prima che appaia una destinazione
Quando le persone immaginano un compilatore, spesso saltano direttamente al backend. Immaginano il momento in cui il codice diventa AVX, PTX, Verilog o WASM. Quel momento conta, ma arriva tardi nella storia. Gran parte del lavoro del compilatore è già avvenuto prima, in fasi silenziose che decidono se ci si può fidare del backend.
Kera inizia con un lexer consapevole di UTF-8 che converte il sorgente in token con tracciamento completo dei dettagli e recupero degli errori. Sembra un lavoro da editor, e lo è. È anche affidabilità della toolchain. Se ogni posizione di byte viene preservata, diagnostica, formattazione, riparsificazione incrementale e comportamento del language server possono allinearsi a ciò che l'utente ha effettivamente scritto. Un compilatore che perde presto la forma del sorgente paga quella perdita ovunque altro.
Il parser è basato su eventi: discesa ricorsiva con parsing delle espressioni Pratt emette eventi Start, Token, Finish ed Error invece di costruire un singolo AST che tutti devono condividere. Il flusso di eventi alimenta il costruttore dell'albero, il formattatore e il language server in modo indipendente. L'albero verde è senza perdite, preservando token e dettagli. Il costruttore di grafi poi attraversa quell'albero, mantiene la risoluzione dei nomi e crea un nodo Region per ogni funzione.
Solo dopo il sorgente diventa il grafo che il compilatore trasporta. Il type checking strutturale valida la compatibilità attraverso i bordi delle operazioni e i vincoli di forma dei tensori. I tipi corrispondono solo quando i loro hash canonici sono identici. Il borrow checker impone un proprietario per valore, nessun riferimento mutabile aliasato e regole di proprietà attraverso gli spazi di memoria. Le annotazioni sugli effetti vengono verificate per coerenza. Gli effetti collaterali non sono lasciati al gusto o alla convenzione.
Il pass manager di ottimizzazione fa poi il lavoro riconoscibile del compilatore: piegamento delle costanti, eliminazione delle sottoespressioni comuni, eliminazione del codice morto, inlining, vettorizzazione e fusione dei loop in un ciclo a punto fisso. La frase importante non è l'elenco dei pass. È ciò che accade dopo ogni pass: un validatore di invarianti del grafo verifica la coerenza degli hash e l'integrità delle dipendenze. L'ottimizzazione può migliorare il grafo, non renderlo misterioso.
Infine, i generatori di codice specifici per target emettono codice nativo, PTX, Verilog o WASM, e il grafo ottimizzato viene serializzato in un file .keg con una tabella di sezioni. Il file inizia con i byte magici KEG\0 e contiene sezioni per stringhe, tipi, attributi, nodi, regioni ed esportazioni. Questo è il percorso che un team deve possedere prima di poter parlare onestamente di target.
I target non sono adesivi su una diapositiva
Un elenco di target è facile da scrivere e difficile da meritare. CPU, GPU, FPGA e WASM si adattano perfettamente a una scheda prodotto. Il lowering verso di essi è dove si nasconde la fattura. Ogni target ha le proprie abitudini, punti di forza e modalità di errore. Un compilatore che vuole esecuzione eterogenea deterministica non può trattare quelle abitudini come un problema di qualcun altro.
Sulle CPU, Kera genera codice macchina nativo per x86-64 e ARM64, con RISC-V Vector nella scheda tecnica. Il percorso x86 seleziona SSE2, AVX2 o AVX-512; il percorso ARM usa NEON; l'allocatore di registri e lo scheduler sono condivisi. L'affermazione del prodotto non è semplicemente che esiste un binario. L'affermazione è che i kernel SIMD specifici per la destinazione vengono selezionati mentre il grafo rimane lo stesso calcolo.
Sulle GPU, il materiale sorgente menziona la generazione di PTX per CUDA e ROCm, con l'uso dei tensor core quando la forma del kernel lo consente. Quest'ultima clausola fa un lavoro importante. L'accelerazione hardware non è polvere magica. Una forma di kernel o si adatta a un percorso tensor core oppure no. Il compilatore deve esporre questo in modo onesto, spostare i dati tra host e device quando necessario, e mantenere comunque l'impronta del grafo come prova di ciò che viene eseguito.
Su FPGA, il percorso di lowering diventa una conversazione hardware: sintesi Verilog, stima delle risorse, analisi dei tempi e scheduling della pipeline per un comportamento prevedibile ed esatto a livello di ciclo. È il tipo di destinazione in cui le approssimazioni sono particolarmente costose. Se l'acquirente ha bisogno di tempo reale rigido, la superficie del compilatore deve parlare in cicli, risorse e pipeline, non solo in velocità. La storia di Kera rende FPGA un backend dello stesso grafo invece di una riscrittura da parte di un altro team.
Su WASM, la pagina cita SIMD a 128 bit per la distribuzione edge e browser. Questo conta perché l'edge è dove la realtà della distribuzione spesso si scontra con la purezza. Un browser, un piccolo dispositivo o un runtime vincolato non possono sempre ospitare lo stesso stack di un server. Un percorso di lowering nativo per il grafo offre al team un modo per portare lo stesso calcolo in quell'ambiente senza trasformare l'edge in un secondo prodotto.
La determinazione deve sopravvivere al successo
La determinazione è facile da promettere in una stanza piccola. Diventa più difficile quando il prodotto ha successo. Arrivano più utenti. Appare più hardware. Un modello passa da un chip all'altro. Una baseline di test scritta per un ambiente deve coprirne un altro. Un'ottimizzazione che sembra innocua cambia l'ordine di una riduzione. Un percorso veloce appare in un runtime ma non in un altro.
La pagina di Kera presenta la determinismo come bit per bit su tutte le piattaforme. Non è un'affermazione estetica. Cambia gli obblighi del prodotto. L'aritmetica primitiva ha una semantica di precisione definita su tutti i target. Le riduzioni hanno criteri di parità definiti, così la pianificazione e l'hardware non cambiano il risultato. Le primitive delle reti neurali hanno implementazioni deterministiche su CPU, GPU, FPGA e WASM. Il grafo significa la stessa cosa ovunque venga eseguito.
Ecco perché il comportamento indefinito, le pause del garbage collector e gli effetti collaterali impliciti non sono piccole preferenze linguistiche. Sono crepe nella superficie di esecuzione. La pagina di Kera afferma: nessuna dipendenza da LLVM, nessun garbage collector, nessun comportamento indefinito. Tipi espliciti, effetti espliciti e proprietà attraverso gli spazi di memoria sono controlli di prodotto. Riducono i punti in cui un risultato può cambiare mentre tutti guardano altrove.
C'è anche un lato umano. Quando un controller robotico cambia comportamento tra dispositivi, quando un flusso di lavoro finanziario arrotonda un centesimo in modo diverso, quando un risultato scientifico non può essere ripetuto da un revisore, o quando una simulazione di gioco diverge tra target, la discussione non riguarda davvero la teoria dei compilatori. Riguarda la fiducia istituzionale. I testi consumer di Kera usano storie semplici perché il problema di fondo è semplice da percepire: stesse istruzioni, stessa risposta, su qualsiasi computer possiedi.
Questa fiducia deve sopravvivere all'accelerazione. Se le prestazioni richiedono ai team di abbandonare il determinismo, il prodotto ha solo spostato il rischio. Kera cerca di mantenere prestazioni e determinismo nello stesso contratto rendendo il grafo l'unità di significato e il percorso verso il target l'unità di esecuzione.
La sicurezza fa parte dell'abbassamento
Le discussioni sui compilatori spesso isolano la sicurezza come lavoro a runtime. Il materiale di Kera non lo fa. Parla di sicurezza basata su capacità, un SecurityManager, un gate di negazione predefinita, un PolicyBuilder, seccomp BPF, namespace Linux e un registro di controllo a prova di manomissione thread-safe in un buffer circolare a capacità fissa. Queste parole appartengono alla storia ingegneristica perché l'abbassamento non termina quando vengono emesse le istruzioni. Il lavoro emesso deve comunque essere eseguito con dei confini.
I token di capacità vengono concessi all'avvio. Le azioni privilegiate richiedono una capacità esplicita. Le regole di accesso sono dichiarate in anticipo. Un filtro BPF limita le syscall. L'isolamento tramite namespace separa le viste di processo, mount e rete. Gli eventi rilevanti per la sicurezza finiscono in un registro di controllo. La frase breve è: nulla è consentito per impostazione predefinita.
Questo è importante per l'esecuzione eterogenea perché i target creano superfici. Un processo CPU, un trasferimento GPU, un percorso FPGA, un runtime browser e un job distribuito non falliscono allo stesso modo. Un prodotto deve mantenere la policy legata al lavoro mentre si sposta. Se il grafo dice cos'è il calcolo, i controlli del runtime dicono cosa il calcolo può fare.
C'è anche una ragione operativa per portare la sicurezza nella storia del compilatore. I team non vogliono un prodotto per la compilazione, un altro per la policy, un altro per la registrazione e un altro per la spiegazione, se le giunzioni tra loro diventano il luogo in cui si nascondono gli incidenti. La pagina di Kera non pretende di risolvere tutta la sicurezza. Fa qualcosa di più circoscritto e più utile: rende i permessi espliciti e sottoposti a controllo nell'ambiente di esecuzione che il compilatore alimenta.
Il lavoro distribuito è ancora abbassamento
La scheda tecnica menziona l'esecuzione distribuita, ring allreduce e il ripristino da checkpoint. In un altro prodotto, potrebbero essere voci da brochure. In Kera, appartengono alla stessa conversazione sull'abbassamento perché il parallelismo cambia la forma di un calcolo. Il parallelismo dei dati, del modello e della pipeline non sono solo modi per andare più veloci. Sono modi per dividere il lavoro senza perdere il significato del grafo originale.
Se un job di grandi dimensioni viene eseguito su molte macchine e un nodo fallisce, il recupero da un checkpoint non è una comodità. È parte del rendere operativo il calcolo. Se i risultati vengono aggregati in modo affidabile, la semantica di aggregazione deve essere definita. Se un grafo è indirizzato per contenuto, il percorso distribuito deve preservare l'identità del grafo piuttosto che inventare una seconda realtà una volta che il job lascia una macchina.
È qui che l'interpretazione del prodotto diventa chiara. Kera non è un livello sintattico con un componente aggiuntivo distribuito. Cerca di rendere il calcolo portabile sia nella forma che nell'hardware: un programma, un grafo, più target di esecuzione e un record che può essere verificato. L'esecuzione distribuita è un altro luogo in cui l'abbassamento deve trasportare il contratto di esecuzione.
Cambia anche la questione del personale. Senza un percorso di compilazione condiviso, un bug che appare solo su una piattaforma può richiedere persone che conoscano contemporaneamente il chip, la toolchain e il runtime. Con un grafo e un percorso condiviso, la domanda è più netta: è cambiato il grafo, è cambiato il percorso di abbassamento o il target ha violato la semantica definita? Domande migliori non eliminano il lavoro difficile. Impediscono al lavoro difficile di diffondersi in modo casuale.
Perché qualcuno deve possedere il percorso
L'abbassamento non può rimanere senza proprietario una volta che trasporta risultati su cui le persone fanno affidamento. Un grafo che gira su CPU, GPU, FPGA e WASM ha bisogno di più di una rappresentazione intelligente. Ha bisogno di documentazione, diagnostica, dichiarazioni sui target, comportamento dell'editor, generazione di codice, controlli di sicurezza, conversazioni di supporto e un modo per dire a un acquirente esattamente cosa è cambiato quando un risultato cambia.
Questo non rende il lavoro meno tecnico. Rende il lavoro tecnico più vincolante. Kera è attualmente un prodotto commerciale Dweve, non una release open-source: tutti i diritti riservati, disponibile con licenza commerciale, realizzato nei Paesi Bassi e pensato per organizzazioni che necessitano del percorso del grafo supportato piuttosto che semplicemente pubblicato. Se la pagina dice Rust 2021, IR del grafo, JIT personalizzato, niente LLVM, CPU/GPU/FPGA/WASM, tracciamento degli effetti, proprietà tra spazi di memoria, sicurezza delle capability, esecuzione distribuita, embedding C e Python, flussi di lavoro CLI, supporto editor e tooling LSP, questi non sono dettagli interni decorativi. Sono obblighi che devono sopravvivere alla valutazione con carichi di lavoro reali.
La scheda delle capability è attenta a questo. Descrive gli obiettivi di progettazione e dice di verificare rispetto al proprio carico di lavoro. Questa moderazione conta. È molto più sana che fingere che ogni numero di benchmark viaggi. Il sistema dovrebbe dare ai team un modo per misurare, ispezionare e confrontare nel proprio ambiente, non chiedere loro di accettare una storia universale di velocità.
Qualcuno deve possedere quel percorso perché ogni target cerca di rendere la sorgente meno universale. Kera è il luogo in cui quelle differenze di target diventano lavoro esplicito del compilatore invece di folklore tramandato dopo il bug report.
Il giorno in cui il grafo diventa il contratto
Immagina di nuovo il bug report originale, ma con Kera già nel flusso di lavoro. Un team finanziario approva un calcolo del rischio. L'impronta del grafo viene registrata. La prima distribuzione gira su CPU. Successivamente, viene introdotto un percorso GPU per l'analisi di portafoglio. Più tardi ancora, un percorso FPGA viene usato per prezzi a latenza inferiore. La domanda in ogni passaggio non è se il nuovo hardware sembra impressionante. La domanda è se lo stesso grafo viene abbassato e se la semantica del target mantiene il risultato identico.
La conversazione cambia. Il team di piattaforma può parlare di pianificazione e costi. Il team del compilatore può parlare di backend. Il team di conformità può parlare dell'impronta. Il proprietario dell'azienda può chiedere se spostare l'hardware è una decisione operativa o una riscrittura. Il prodotto dà a tutti loro un unico oggetto a cui puntare.
Questo è il valore silenzioso di un IR content-addressed. Trasforma l'esecuzione cross-platform da una serie di spiegazioni persuasive in un registro. Rende il grafo una ricevuta. Consente allo stesso file .keg di alimentare ogni backend. Dà all'organizzazione una baseline che appartiene al calcolo piuttosto che a una singola macchina.
Nessun compilatore può eliminare la necessità del giudizio ingegneristico. I team devono ancora selezionare i target con attenzione, testare i workload in modo onesto, comprendere i limiti dei target e decidere quali domini richiedono una perfetta identità bit per bit. Ma Kera può rendere esplicite queste decisioni. Può impedire che una migrazione delle performance diventi accidentalmente una migrazione semantica.
La lezione del lowering
Kera è importante perché la parte difficile non è avere un'idea intelligente di lowering. La parte difficile è portare quell'idea attraverso tutti i luoghi noiosi in cui l'ingegneria diventa affidabile o diventa folklore: diagnostica, alberi verdi, hash dei tipi, borrow checking, ottimizzazione a punto fisso, validazione degli invarianti, serializzazione .keg, emissione del backend, policy di sicurezza, log di audit, supporto editor ed esecuzione specifica per target.
Il lowering è un lavoro perché ogni target cerca di rendere il source meno universale. L'ingegneria del compilatore deterministico è la disciplina che rifiuta di permettere che ciò accada in silenzio. La CPU vuole vettori. La GPU vuole kernel. La FPGA vuole cicli. WASM vuole vincoli. L'organizzazione vuole una sola risposta. Il compito di Kera è preservare il significato del calcolo lasciando che ogni target faccia ciò in cui è bravo.
Questa è una forma ingegneristica, non un'astrazione da paper. Ha un acquirente, una modalità di errore, un formato di file, un percorso del compilatore e conseguenze operative. La promessa non è che l'hardware diventi semplice. La promessa è che la complessità è rappresentata, abbassata, verificata e posseduta.
Quando arriva il prossimo numero quasi giusto, il team non dovrebbe dover iniziare dal folklore su quale macchina ha eseguito cosa. Dovrebbe iniziare dal grafo. La fingerprint corrispondeva? Quale backend ha emesso il codice? Quali capacità sono state concesse? Quali semantiche del target sono state applicate? Quale artefatto è stato archiviato? Queste sono domande di prodotto. Kera esiste perché sono anche domande da compilatore.