Kera y el trabajo de bajar

Una historia de compiladores sobre el trabajo poco glamuroso entre un grafo limpio y una máquina real: reducción determinista, propiedad del destino y...

Kera y el trabajo de bajar

El informe de error que no era un error

La historia suele empezar con un número que es casi correcto. No está muy desviado. No está roto de una manera que ponga el panel en rojo. Casi correcto de la manera cara: el tipo de error que permite que un equipo lance, otro equipo dude y un auditor pregunte por qué cambió la respuesta cuando el mismo modelo se movió de una máquina a otra.

Una compilación se ejecuta en un servidor x86 y produce un valor. La siguiente compilación se ejecuta en un portátil ARM y produce un valor lo bastante cercano como para que una demo sobreviva. Una ruta de GPU es más rápida, pero redondea de manera diferente a lo largo del cálculo. Una ruta de FPGA es atractiva por el tiempo, pero de repente el equipo de software está teniendo una conversación de hardware. Todos pueden explicar una pequeña parte de la diferencia. Nadie es dueño de la ruta completa desde el origen hasta el resultado.

Ese es el espacio para el que está construida Kera. No la idea del modelo, no la diapositiva de marketing, no un artículo sobre un compilador futuro, sino el trabajo de compilación que comienza cuando un equipo dice que el mismo cálculo debe ejecutarse en diferentes máquinas y seguir significando lo mismo. Esa obligación necesita un dueño. La promesa del compilador no es que el lowering sea elegante. La promesa es que el lowering se trate como trabajo.

El lowering es la parte de un compilador donde la buena intención del lenguaje se convierte en instrucciones que un objetivo puede ejecutar. También es donde las afirmaciones vagas se vuelven visibles. Si se supone que el resultado debe ser idéntico bit a bit entre CPU, GPU, FPGA y WebAssembly, entonces el compilador no puede encogerse de hombros ante las diferencias de los objetivos. Tiene que llevar suficiente estructura para decidir qué puede cambiar y qué no debe cambiar. Tiene que conocer espacios de memoria, efectos, opcodes, movimiento de datos, límites de los objetivos y los recibos que demuestran que dos compilaciones son el mismo cálculo.

La versión tentadora de la historia es llamar a esto una capa de portabilidad. Eso es demasiado poco. La portabilidad dice que el programa se ejecuta en otro lugar. Kera apunta a la afirmación más difícil: el programa se representa como un grafo con direccionamiento por contenido, se compila en un artefacto .keg, se reduce a múltiples backends y sigue produciendo la misma respuesta. La cocina cambia. La receta no.

El término medio que nadie quiere vender

La primera conversación con un comprador serio rara vez comienza con la sintaxis. Comienza con un desastre. Hay un modelo que importa. Hay una simulación que solía ser investigación y se ha vuelto operativa. Hay un cálculo de riesgo cuyo resultado ya no puede variar según la máquina. Hay un despliegue en el borde que no puede llevar el runtime completo de la nube. Hay un equipo que quiere un acelerador pero no puede permitirse una reescritura cada vez que el acelerador cambia.

La mayoría de las herramientas hacen que esto parezca un problema de despliegue. Elige el objetivo, exporta el modelo, parchea el runtime, acepta cierta varianza y luego escribe un documento que explique las excepciones. El documento crece. La matriz de pruebas crece. El número de especialistas crece. Con el tiempo, la organización paga por la heterogeneidad dos veces: una cuando compra el hardware y otra cuando intenta demostrar que el hardware hizo el mismo trabajo.

Kera empieza desde el extremo opuesto. La página de Kera lo llama un lenguaje de sistemas con tipado estático con un IR nativo de grafos y direccionamiento por contenido. Esa frase importa porque el grafo no es un diagrama decorativo de compilador. Es el objeto ejecutable. El código fuente se reduce a grafos acíclicos dirigidos de operaciones, almacenados como archivos .keg. Cada nodo lleva una estructura definida. El trabajo duplicado se puede eliminar estructuralmente. El mismo grafo se puede reducir a CPU, GPU, FPGA y WASM sin tratar cada objetivo como un pequeño universo separado.

Por eso Kera tiene que comportarse como una infraestructura de compilación seria y no como un diagrama. El incómodo punto medio es donde vive el cliente. La tokenización tiene que preservar las posiciones de los bytes. El análisis sintáctico tiene que alimentar tanto las herramientas como la compilación. La comprobación de tipos tiene que rechazar los desajustes de forma y de borde antes de que se conviertan en eventos en tiempo de ejecución. La comprobación de préstamos tiene que entender la propiedad a través de la memoria del host, del dispositivo, fija y unificada. La optimización tiene que ser repetible. La serialización tiene que dar a la organización un artefacto que pueda conservar.

Nada de eso es un titular llamativo. Es mucho más útil que un titular. Significa que cuando una carga de trabajo pasa del desarrollo a la producción, o de una base de CPU a una ruta de GPU, el compilador tiene algo estable con lo que comparar: la huella del grafo. Si la huella coincide, el trabajo es el mismo trabajo. Ese es un punto de control compartido que un ingeniero, un comprador y un auditor pueden entender todos.

Un grafo es un recibo, no una imagen

La forma antigua de explicar un programa es mostrar el código fuente y pedir al lector que confíe en el compilador. La página de Kera no deja de volver a un objeto diferente: el grafo. Cada operación es un nodo. Los bordes expresan dependencias. El grafo tiene contenido. El contenido tiene un hash. El hash se convierte en una huella que viaja a través de la compilación.

El grafo es el objeto duradero: código fuente, tipos, regiones, hashes y el artefacto que viaja.

Esto suena a maquinaria interna hasta que te pones al lado de un equipo de cumplimiento. Un equipo de cumplimiento no quiere saber que un proveedor tiene una buena arquitectura de compilador. Quiere saber si el cálculo aprobado el mes pasado es el cálculo que se ejecuta hoy. La huella del grafo es el puente entre esos mundos. No es una captura de pantalla del compilador. Es una forma compacta de decir: este conjunto de operaciones, estos tipos, estas entradas, estos atributos, esta estructura de dependencias.

El modelo de nodos es deliberadamente estricto. Un nodo es una versión de esquema, un código de operación, descriptores de salida, hashes de entrada y mapas de atributos dispuestos en orden canónico. Los bytes se someten a hash con SHA3-256 bajo prefijos separados por dominio, de modo que los hashes de nodo, región y grafo viven en espacios de nombres separados. Si dos nodos tienen el mismo código de operación, entradas y atributos, tienen el mismo hash. Insertar un duplicado devuelve la entrada existente. La eliminación de subexpresiones comunes se convierte en una consecuencia de la representación, no en una pasada heroica añadida después.

Ese es el tipo de detalle que convierte un sistema de ingeniería en algo en lo que la gente puede confiar. Un comprador no está comprando un diagrama de compilador. Un comprador está comprando menos discusiones sobre si un cálculo ha cambiado. Un desarrollador no está comprando un eslogan sobre IA determinista. Un desarrollador está comprando un formato de grafo donde la igualdad puede comprobarse, almacenarse y usarse mediante herramientas. El archivo .keg no es, por tanto, una ocurrencia tardía de la implementación. Es un registro portátil del trabajo.

Las ocho pequeñas puertas antes de que aparezca un objetivo

Cuando la gente imagina un compilador, suele saltar directamente al backend. Se imaginan el momento en que el código se convierte en AVX, PTX, Verilog o WASM. Ese momento importa, pero llega tarde en la historia. Para entonces, la mayor parte del trabajo del compilador ya ha ocurrido, en etapas silenciosas que deciden si se puede confiar en el backend.

Los backends llegan tarde. La mayor parte de la confianza se gana antes, en las pequeñas etapas del compilador que preservan el significado.

Kera comienza con un lexer compatible con UTF-8 que convierte el código fuente en tokens con seguimiento completo de trivia y recuperación de errores. Eso suena a fontanería de editor, y lo es. También es fiabilidad de la cadena de herramientas. Si se preserva cada posición de byte, los diagnósticos, el formateo, el reanálisis incremental y el comportamiento del servidor de lenguaje pueden alinearse con lo que el usuario escribió realmente. Un compilador que pierde la forma del código fuente al principio paga esa pérdida en todas partes.

El parser está basado en eventos: el descenso recursivo con análisis de expresiones Pratt emite eventos Start, Token, Finish y Error en lugar de construir un único AST que todos tengan que compartir. El flujo de eventos alimenta al constructor de árboles, al formateador y al servidor de lenguaje de forma independiente. El árbol verde es sin pérdidas y preserva tokens y trivia. El constructor de grafos recorre ese árbol, mantiene la resolución de nombres y crea un nodo Region por función.

Solo después de eso el código fuente se convierte en el grafo que el compilador transporta. La comprobación estructural de tipos valida la compatibilidad a través de los bordes de operación y las restricciones de forma de los tensores. Los tipos coinciden solo cuando sus hashes canónicos son idénticos. El comprobador de préstamos impone un propietario por valor, sin referencias mutables con alias, y reglas de propiedad entre espacios de memoria. Las anotaciones de efectos se comprueban para garantizar la coherencia. Los efectos secundarios no se dejan al gusto ni a la convención.

El gestor de pases de optimización hace entonces el trabajo de compilador reconocible: plegado de constantes, eliminación de subexpresiones comunes, eliminación de código muerto, inline, vectorización y fusión de bucles en un bucle de punto fijo. La frase importante no es la lista de pases. Es lo que ocurre después de cada pase: un validador de invariantes del grafo comprueba la consistencia del hash y la integridad de las dependencias. Se permite que la optimización mejore el grafo, no que lo vuelva misterioso.

Finalmente, los generadores de código específicos del destino emiten código nativo, PTX, Verilog o WASM, y el grafo optimizado se serializa a un archivo .keg con una tabla de secciones. El archivo comienza con los bytes mágicos KEG\0 y lleva secciones para cadenas, tipos, atributos, nodos, regiones y exportaciones. Ese es el camino que un equipo debe recorrer antes de poder hablar honestamente de los destinos.

Los destinos no son pegatinas en una diapositiva

Una lista de destinos es fácil de escribir y difícil de merecer. CPU, GPU, FPGA y WASM encajan perfectamente en una tarjeta de producto. El descenso a ellos es donde se esconde la factura. Cada destino tiene sus propios hábitos, fortalezas y modos de fallo. Un compilador que quiera ejecución heterogénea determinista no puede tratar esos hábitos como un problema de otro.

CPU, GPU, FPGA y WASM no son etiquetas. Son mundos destino que el mismo grafo debe sobrevivir.

En CPU, Kera genera código máquina nativo para x86-64 y ARM64, con RISC-V Vector en la ficha técnica. La ruta x86 selecciona SSE2, AVX2 o AVX-512; la ruta ARM usa NEON; el asignador de registros y el planificador son compartidos. La afirmación del producto no es solo que exista un binario. La afirmación es que se seleccionan kernels SIMD específicos del destino mientras el grafo sigue siendo el mismo cálculo.

En GPU, el material de origen menciona la generación de PTX para CUDA y ROCm, con núcleos tensoriales cuando la forma del kernel lo permite. Esa última cláusula hace trabajo. La aceleración por hardware no es polvo mágico. Una forma de kernel o encaja en una ruta de núcleos tensoriales o no. El compilador tiene que exponerlo con honestidad, mover datos entre host y dispositivo cuando sea necesario, y aun así mantener la huella del grafo como prueba de lo que se está ejecutando.

En FPGA, la ruta de lowering se convierte en una conversación de hardware: síntesis Verilog, estimación de recursos, análisis de tiempos y planificación de pipeline para un comportamiento predecible y exacto en ciclos. Es el tipo de destino donde las vaguedades resultan especialmente caras. Si el comprador necesita tiempo real estricto, la superficie del compilador tiene que hablar en ciclos, recursos y pipelines, no solo en velocidad. La historia de Kera convierte FPGA en un backend del mismo grafo en lugar de una reescritura por otro equipo.

En WASM, la página menciona SIMD de 128 bits para despliegue en edge y navegador. Eso importa porque edge es donde la realidad del despliegue suele chocar con la pureza. Un navegador, un dispositivo pequeño o un runtime limitado no siempre pueden alojar la misma pila que un servidor. Una ruta de lowering nativa del grafo da al equipo una forma de llevar el mismo cálculo a ese entorno sin convertir el edge en un segundo producto.

El determinismo tiene que sobrevivir al éxito

El determinismo es fácil de prometer en una sala pequeña. Se vuelve más difícil cuando el producto tiene éxito. Llegan más usuarios. Aparece más hardware. Un modelo pasa de un chip a otro. Una línea base de pruebas escrita para un entorno tiene que cubrir otro. Una optimización que parece inofensiva cambia el orden de una reducción. Aparece una ruta rápida en un runtime pero no en otro.

El determinismo es un bucle, no un eslogan: construir, ejecutar, comparar, corregir y mantener el grafo estable.

La página de Kera presenta el determinismo como bit a bit entre plataformas. No es una afirmación cosmética. Cambia las obligaciones del producto. La aritmética primitiva tiene semántica de precisión definida en todos los objetivos. Las reducciones tienen desempates definidos para que la planificación y el hardware no cambien el resultado. Los primitivos de redes neuronales tienen implementaciones deterministas en CPU, GPU, FPGA y WASM. El grafo significa lo mismo dondequiera que se ejecute.

Por eso el comportamiento indefinido, las pausas del recolector de basura y los efectos secundarios implícitos no son pequeñas preferencias de lenguaje. Son grietas en la superficie de ejecución. La página de Kera dice sin dependencia de LLVM, sin recolector de basura, sin comportamiento indefinido. Tipos explícitos, efectos explícitos y propiedad entre espacios de memoria son controles del producto. Reducen los lugares donde un resultado puede cambiar mientras todos miran a otro lado.

También hay un lado humano en esto. Cuando un controlador de robótica cambia de comportamiento entre dispositivos, cuando un flujo de trabajo financiero redondea un céntimo de forma diferente, cuando un resultado científico no puede ser repetido por un revisor, o cuando una simulación de juego diverge entre objetivos, el argumento no trata realmente de teoría de compiladores. Trata de la confianza institucional. El texto comercial de Kera usa historias sencillas porque el problema subyacente es sencillo de sentir: mismas instrucciones, misma respuesta, en cualquier ordenador que tengas.

Esa confianza debe sobrevivir a la aceleración. Si el rendimiento exige que los equipos abandonen el determinismo, el producto solo ha movido el riesgo. Kera intenta mantener el rendimiento y el determinismo en el mismo contrato haciendo del grafo la unidad de significado y de la ruta objetivo la unidad de ejecución.

La seguridad es parte del descenso

Las discusiones sobre compiladores a menudo aíslan la seguridad como trabajo de tiempo de ejecución. El material de origen de Kera no lo hace. Habla de seguridad basada en capacidades, un SecurityManager, una puerta de denegación por defecto, un PolicyBuilder, seccomp BPF, espacios de nombres de Linux y un registro de auditoría a prueba de manipulaciones y seguro para subprocesos en un búfer circular de capacidad fija. Esas palabras pertenecen a la historia de ingeniería porque el descenso no termina cuando se emiten las instrucciones. El trabajo emitido aún tiene que ejecutarse con límites.

Los tokens de capacidad se conceden al inicio. Las acciones privilegiadas requieren una capacidad explícita. Las reglas de acceso se declaran de antemano. Un filtro BPF restringe las llamadas al sistema. El aislamiento de espacios de nombres separa las vistas de proceso, montaje y red. Los eventos relevantes para la seguridad llegan a un registro de auditoría. La frase corta es: nada está permitido por defecto.

Eso importa para la ejecución heterogénea porque los objetivos crean superficies. Un proceso de CPU, una transferencia de GPU, una ruta de FPGA, un tiempo de ejecución de navegador y un trabajo distribuido no fallan de maneras idénticas. Un producto tiene que mantener la política adjunta al trabajo mientras se mueve. Si el grafo dice qué es el cálculo, los controles del tiempo de ejecución dicen qué puede hacer el cálculo.

También hay una razón operativa para llevar la seguridad a la historia del compilador. Los equipos no quieren un producto para la compilación, otro para la política, otro para el registro y otro para la explicación si las costuras entre ellos se convierten en el lugar donde se esconden los incidentes. La página de Kera no afirma resolver toda la seguridad. Hace algo más limitado y más útil: hace que los permisos sean explícitos y auditados en el entorno de ejecución que alimenta el compilador.

El trabajo distribuido sigue siendo descenso

La ficha técnica menciona la ejecución distribuida, allreduce en anillo y recuperación de puntos de control. En otro producto, esos podrían ser elementos de folleto. En Kera, pertenecen a la misma conversación sobre descenso porque el paralelismo cambia la forma de un cálculo. El paralelismo de datos, de modelo y de pipeline no son solo formas de ir más rápido. Son formas de dividir el trabajo sin perder el significado del grafo original.

Si un trabajo grande se ejecuta en muchas máquinas y un nodo falla, recuperarse desde un punto de control no es una conveniencia. Es parte de hacer que el cálculo sea operativo. Si los resultados se agregan de forma fiable, hay que definir la semántica de la agregación. Si un grafo está direccionado por contenido, la ruta distribuida debe preservar la identidad del grafo en lugar de inventar una segunda realidad una vez que el trabajo sale de una máquina.

Aquí es donde la interpretación del producto se vuelve clara. Kera no es una capa de sintaxis con un complemento distribuido. Intenta hacer que el cálculo sea portable tanto en forma como en hardware: un programa, un grafo, múltiples destinos de ejecución y un registro que se pueda verificar. La ejecución distribuida es un lugar más donde el lowering tiene que transportar el contrato de ejecución.

También cambia la historia de la dotación de personal. Sin una ruta de compilación compartida, un error que solo aparece en una plataforma puede requerir personas que conozcan el chip, la cadena de herramientas y el runtime a la vez. Con un grafo y una ruta compartida, la pregunta es más precisa: ¿cambió el grafo, cambió la ruta de lowering o el destino violó la semántica definida? Las mejores preguntas no eliminan el trabajo duro. Evitan que el trabajo duro se extienda al azar.

Por qué alguien tiene que ser dueño de la ruta

El lowering no puede quedarse sin dueño una vez que conlleva resultados de los que la gente depende. Un grafo que se ejecuta en CPU, GPU, FPGA y WASM necesita más que una representación inteligente. Necesita documentación, diagnósticos, afirmaciones sobre los destinos, comportamiento del editor, generación de código, controles de seguridad, conversaciones de soporte y una forma de decirle al comprador exactamente qué cambió cuando cambia un resultado.

Eso no hace que el trabajo sea menos técnico. Hace que el trabajo técnico sea más vinculante. Kera es actualmente un producto comercial de Dweve, no una versión de código abierto: todos los derechos reservados, disponible bajo licencia comercial, hecho en los Países Bajos y pensado para organizaciones que necesitan que la ruta del grafo esté soportada en lugar de simplemente publicada. Si la página dice Rust 2021, IR de grafo, JIT personalizado, sin LLVM, CPU/GPU/FPGA/WASM, seguimiento de efectos, propiedad entre espacios de memoria, seguridad por capacidades, ejecución distribuida, incrustación en C y Python, flujos de trabajo CLI, soporte de editor y herramientas LSP, esos no son detalles internos decorativos. Son obligaciones que deben sobrevivir a la evaluación con cargas de trabajo reales.

La hoja de capacidades es cuidadosa con esto. Describe los objetivos de diseño y dice que se verifiquen con tu carga de trabajo. Esa contención importa. Es mucho más saludable que fingir que cada número de referencia viaja. El sistema debería dar a los equipos una forma de medir, inspeccionar y comparar en su propio entorno, no pedirles que acepten una historia universal de velocidad.

Alguien tiene que ser dueño de esa ruta porque cada destino intenta hacer que la fuente sea menos universal. Kera es el lugar donde esas diferencias de destino se convierten en trabajo explícito del compilador en lugar de folclore que se transmite después del informe de error.

El día en que el grafo se convierte en el contrato

Imagina de nuevo el informe de error original, pero con Kera ya en el flujo de trabajo. Un equipo de finanzas aprueba un cálculo de riesgo. La huella del grafo se registra. El primer despliegue se ejecuta en CPU. Más tarde, se introduce una ruta GPU para el análisis de carteras. Más tarde aún, se utiliza una ruta FPGA para la valoración de menor latencia. La pregunta en cada paso no es si el nuevo hardware suena impresionante. La pregunta es si se está haciendo lowering del mismo grafo y si la semántica del destino mantiene el resultado idéntico.

La conversación cambia. El equipo de plataforma puede hablar de programación y coste. El equipo de compiladores puede hablar de backends. El equipo de cumplimiento puede hablar de la huella. El propietario del negocio puede preguntar si mover hardware es una decisión operativa o una reescritura. El producto les da a todos un único objeto al que señalar.

Ese es el valor silencioso de una IR direccionada por contenido. Convierte la ejecución multiplataforma de una serie de explicaciones persuasivas en un registro. Convierte el grafo en un recibo. Permite que el mismo archivo .keg alimente todos los backends. Le da a la organización una línea de base que pertenece al cómputo, no a una sola máquina.

Ningún compilador puede eliminar la necesidad del criterio de ingeniería. Los equipos aún deben seleccionar los objetivos con cuidado, probar las cargas de trabajo con honestidad, comprender los límites de los objetivos y decidir qué dominios exigen una igualdad bit a bit. Pero Kera puede hacer explícitas esas decisiones. Puede evitar que una migración de rendimiento se convierta en una migración semántica por accidente.

La lección del lowering

Kera importa porque la parte difícil no es tener una idea inteligente de lowering. La parte difícil es llevar esa idea a través de todos los lugares monótonos donde la ingeniería se vuelve confiable o se convierte en folclore: diagnósticos, árboles verdes, hashes de tipos, comprobación de préstamos, optimización de punto fijo, validación de invariantes, serialización .keg, emisión de backends, política de seguridad, registros de auditoría, soporte de editores y ejecución específica del objetivo.

El lowering es trabajo porque cada objetivo intenta hacer que la fuente sea menos universal. La ingeniería de compiladores determinista es la disciplina de negarse a permitir que eso ocurra en silencio. La CPU quiere vectores. La GPU quiere kernels. La FPGA quiere ciclos. WASM quiere restricciones. La organización quiere una sola respuesta. El trabajo de Kera es preservar el significado del cómputo mientras permite que cada objetivo haga aquello en lo que es bueno.

Esa es una forma de ingeniería, no una abstracción de papel. Tiene un comprador, un modo de fallo, un formato de archivo, una ruta de compilador y consecuencias operativas. La promesa no es que el hardware se vuelva simple. La promesa es que la complejidad está representada, reducida, comprobada y gestionada.

Cuando llegue el siguiente número casi correcto, el equipo no debería tener que empezar con folclore sobre qué máquina ejecutó qué. Debería empezar con el grafo. ¿Coincidió la huella? ¿Qué backend emitió el código? ¿Qué capacidades se concedieron? ¿Qué semántica de objetivo se aplicó? ¿Qué artefacto se almacenó? Esas son preguntas de producto. Kera existe porque también son preguntas de compilador.