Cómo el motor le habla a cualquier modelo que traigas
Trae cualquier modelo que quieras. La capa de IA de RakuAI son interfaces, no importaciones, así que el mercado de modelos puede tener su berrinche trimestral sobre quién es el mejor y a tu motor nunca tiene que importarle.
Alguien me hizo una pregunta aguda este fin de semana. «Si la IA es una primitiva del runtime dentro de tu motor, entonces el motor está encerrado con el modelo que conectaste. La próxima vez que el proveedor del modelo se mueva, tienes que reescribir el motor.»
Preocupación justa. Y equivocada, pero justa.
La primitiva de IA en el runtime no es un modelo específico. Es una interfaz. Cinco subsistemas separados, cada uno con su propia API en C, cada uno direccionable desde el resto del motor sin que nadie tenga que saber qué modelo, qué pesos, o qué ruta de inferencia está produciendo la respuesta. Los modelos viven detrás de la interfaz. Quienes invocan viven delante de ella. El contrato entre ambos es pequeño, estable, y tonto a propósito.
Esta es la entrada sobre cómo se traza esa frontera, y por qué sigo sacándole más valor a haberla trazado.
A qué apunta realmente «primitiva de IA»
La capa de IA en nuestro runtime no es una sola cosa. Son cinco.
- Árboles de comportamiento. La capa de ejecución determinista. Le dice a un agente qué hacer una vez que se conoce la intención.
- Malla de navegación y pathfinding. La capa de «cómo me muevo a través de este espacio».
- Simulación de multitudes. La capa de «cómo muchos agentes se evitan entre sí y se comportan de forma coherente en conjunto».
- Sistemas sensoriales y percepción. La capa de «qué observa el agente».
- Árboles de decisión y lógica de IA compleja. La capa de «dado todo lo que sé, qué quiero hacer».
Cada uno es su propio subsistema. Cada uno se lanza como su propia DLL. Cada uno tiene una API pública en C. Ninguno importa un modelo específico. Todos le hablan al resto del motor a través de sus respectivas interfaces, y se hablan entre sí de la misma forma.
Cuando un archivo de experiencia .raku dice "ai_behavior": "strafe", no está nombrando un modelo. Está nombrando un comportamiento registrado. El runtime resuelve la cadena. La cadena se mapea a una implementación. La implementación puede ser un árbol de comportamiento, un árbol de decisión, una política aprendida, o una heurística escrita a mano. Quien invoca no lo sabe. El archivo no lo sabe. La implementación se puede intercambiar sin tocar ninguno de los dos.
Ese es todo el truco.
La capa de gestión de modelos es su propio subsistema
Cuando necesitamos inferencia real de aprendizaje automático dentro del runtime, no la atornillamos en uno de los cinco subsistemas de arriba. Agregamos una sexta preocupación con su propia superficie de API: gestión de modelos.
// Roughly what the surface looks like, simplified for the post.
RakuModelHandle raku_ai_load_model(const char* model_id, RakuModelOptions opts);
RakuInferenceResult raku_ai_infer(RakuModelHandle h, const RakuTensor* input);
void raku_ai_unload_model(RakuModelHandle h);
Un nodo de árbol de comportamiento que quiere invocar un modelo pasa por esta API. No importando un SDK de proveedor. No enlazando con un runtime específico. Pidiéndole un handle al subsistema de gestión de modelos y usándolo.
Eso significa que el subsistema de gestión de modelos es el único lugar en la base de código que sabe sobre formatos de modelo específicos, proveedores, o frameworks de inferencia. En cualquier otro lugar del motor solo se ven handles y tensores. Cambia un modelo. Cambia un runtime. Cambia un proveedor. Nada más tiene que cambiar.
Este es un trabajo de infraestructura poco glamoroso. También es lo que nos permite no entrar en pánico cuando el ecosistema de IA tiene su berrinche trimestral sobre cuál modelo es el nuevo mejor.
La frontera también protege el formato de archivo
Mira el bloque ai en la parte superior de cualquier archivo de experiencia .raku. Tiene claves como:
"ai": {
"dda_enabled": true,
"target_flow_state": 0.7,
"profiler_mode": "active"
}
Ninguna de esas nombra un modelo. Nombran capacidades. «El ajuste dinámico de dificultad está activado. El runtime debería apuntar a un estado de flujo de 0.7. El perfilador está activo.» El runtime decide qué subsistemas se involucran para entregar esas capacidades. Si la respuesta correcta este trimestre es un árbol de comportamiento, eso es lo que corre. Si el próximo trimestre se convierte en un pequeño modelo en el dispositivo, el archivo no cambia.
Cada frontera de interfaz que trazas en un sistema es un lugar donde el cambio futuro puede ocurrir sin romper a quienes invocan. Las trazamos de forma agresiva desde el principio. Ahora estamos gastando los ahorros.
Por qué esto sigue importando más, no menos
Tres razones.
Uno. El mercado de modelos sigue moviéndose. El mejor modelo del año pasado es el caro de este año. El mejor de este año es el obsoleto del próximo. Los equipos que codificaron a fuego un proveedor de modelo en su motor ya han hecho esa integración dos o tres veces a estas alturas. Los equipos que pusieron una interfaz de gestión de modelos de por medio la han hecho una vez.
Dos. El dispositivo local importa más de lo que importaba. La interfaz nos permite correr el mismo ai_behavior: "strafe" contra un modelo del lado del servidor en desarrollo y un modelo en el dispositivo en producción. Quien invoca no lo sabe. Esa flexibilidad es la única razón por la que la inferencia en el dispositivo es viable sin reescribir la capa de experiencia.
Tres. Los asistentes de IA en el ciclo de desarrollo se benefician de fronteras limpias. Cuando le pido a un asistente que agregue un nuevo comportamiento, el contrato que tiene que respetar es la interfaz de comportamiento registrado. No un enredo de SDKs de proveedores. Cuanto más limpia la interfaz, más rápido el asistente produce código correcto, y menor el costo de revisión de mi lado.
Lo que es difícil de esto
Los costos honestos.
El diseño de interfaces toma más tiempo que la implementación. Es genuinamente tentador saltarse el paso de la interfaz y simplemente escribir la versión que funciona. Resiste. Cada atajo que tomas aquí lo pagas después cuando necesitas intercambiar la implementación.
Tienes que ser disciplinado sobre qué entra en la interfaz. Cada parámetro es un contrato que no puedes romper fácilmente. Agrega menos de los que crees que necesitas. Espera a que el segundo caso de uso te muestre qué es realmente general. La primera versión de la interfaz de IA tenía cinco parámetros que resultaron no pertenecer ahí. Quitarlos después fue doloroso.
Los comportamientos registrados necesitan versionado. Cuando "strafe" significa una cosa en una compilación del runtime y algo ligeramente distinto en la siguiente, quienes invocan se enteran por regresiones de jugabilidad. Versionamos los comportamientos y fijamos los archivos .raku a versiones del runtime. Es molesto. Es necesario.
A veces realmente quieres la dependencia. Este es el hereje. Hay casos donde un modelo específico tiene una capacidad específica que ninguna interfaz genérica puede expresar. La movida honesta es extender la interfaz para que la capacidad se vuelva genérica, no filtrar el modelo hacia quien invoca. Nos hemos sorprendido queriendo tomar el atajo más de una vez.
El argumento arquitectónico es sencillo. La IA es una primitiva del runtime. El formato de archivo que la impulsa nombra capacidades, no modelos. El runtime resuelve capacidades a cualquier subsistema que actualmente las entregue. El cable entre las tres capas es la interfaz. La interfaz es pequeña, estable, y tonta a propósito.
Esa es toda la entrada.
De vuelta a construir.
Trae tu modelo. La interfaz está esperando.
RakuAI le habla a cualquier modelo a través de handles y tensores: del lado del servidor en desarrollo, en el dispositivo en producción, y quien invoca nunca nota la diferencia. Descubre cómo tus pesos se conectan a un runtime espacial construido para sobrevivir al mercado de modelos.