Comment le moteur parle à n'importe quel modèle que vous apportez
Apportez le modèle que vous voulez. La couche IA de RakuAI est faite d'interfaces, pas d'imports — donc le marché des modèles peut faire sa crise trimestrielle sur qui est le meilleur, et votre moteur n'a jamais à s'en soucier.
Quelqu’un m’a posé une question pertinente ce week-end. « Si l’IA est une primitive du runtime à l’intérieur de votre moteur, alors le moteur est enfermé dans le modèle que vous avez câblé. La prochaine fois que le fournisseur de modèle bouge, vous devez réécrire le moteur. »
Une inquiétude légitime. Et fausse, mais légitime.
La primitive IA dans le runtime n’est pas un modèle spécifique. C’est une interface. Cinq sous-systèmes distincts, chacun avec sa propre API C, chacun adressable depuis le reste du moteur sans que personne n’ait besoin de savoir quel modèle, quels poids, ou quel chemin d’inférence produit la réponse. Les modèles vivent derrière l’interface. Les appelants vivent devant. Le contrat entre eux est petit, stable, et volontairement simpliste.
C’est le billet sur la façon dont cette frontière est tracée, et pourquoi j’en tire toujours plus de valeur maintenant que je l’ai tracée.
Ce que « primitive IA » désigne réellement
La couche IA de notre runtime n’est pas une seule chose. Ce sont cinq.
- Arbres de comportement. La couche d’exécution déterministe. Dit à un agent quoi faire une fois l’intention connue.
- Maillage de navigation et pathfinding. La couche « comment je me déplace dans cet espace ».
- Simulation de foule. La couche « comment plusieurs agents s’évitent et se comportent de façon cohérente ensemble ».
- Systèmes sensoriels et perception. La couche « qu’est-ce que l’agent observe ».
- Arbres de décision et logique IA complexe. La couche « compte tenu de tout ce que je sais, que veux-je faire ».
Chacune est son propre sous-système. Chacune est livrée en tant que sa propre DLL. Chacune a une API C publique. Aucune n’importe un modèle spécifique. Elles parlent toutes au reste du moteur via leurs interfaces respectives, et elles se parlent entre elles de la même façon.
Quand un fichier d’expérience .raku dit "ai_behavior": "strafe", il ne nomme pas un modèle. Il nomme un comportement enregistré. Le runtime résout la chaîne. La chaîne correspond à une implémentation. L’implémentation peut être un arbre de comportement, un arbre de décision, une politique apprise, ou une heuristique écrite à la main. L’appelant ne le sait pas. Le fichier ne le sait pas. L’implémentation peut être échangée sans toucher ni l’un ni l’autre.
C’est toute l’astuce.
La couche de gestion de modèles est son propre sous-système
Quand nous avons eu besoin d’une véritable inférence de machine learning à l’intérieur du runtime, nous ne l’avons pas boulonnée dans l’un des cinq sous-systèmes ci-dessus. Nous avons ajouté une sixième préoccupation avec sa propre surface d’API : la gestion de modèles.
// Grosso modo à quoi ressemble la surface, simplifiée pour ce billet.
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 nœud d’arbre de comportement qui veut appeler un modèle passe par cette API. Pas en important un SDK fournisseur. Pas en liant à un runtime spécifique. En demandant une poignée au sous-système de gestion de modèles et en l’utilisant.
Cela signifie que le sous-système de gestion de modèles est le seul endroit dans la base de code qui connaît les formats de modèles spécifiques, les fournisseurs, ou les frameworks d’inférence. Partout ailleurs dans le moteur, on voit des poignées et des tenseurs. Changez de modèle. Changez de runtime. Changez de fournisseur. Rien d’autre n’a besoin de changer.
C’est un travail d’infrastructure peu glamour. C’est aussi ce qui nous permet de ne pas paniquer quand l’écosystème IA fait sa crise trimestrielle sur quel modèle est le nouveau meilleur.
La frontière protège aussi le format de fichier
Regardez le bloc ai en haut de n’importe quel fichier d’expérience .raku. Il a des clés comme :
"ai": {
"dda_enabled": true,
"target_flow_state": 0.7,
"profiler_mode": "active"
}
Aucune de ces clés ne nomme un modèle. Elles nomment des capacités. « L’ajustement dynamique de difficulté est activé. Le runtime doit viser un état de flow de 0,7. Le profileur est actif. » Le runtime décide quels sous-systèmes sont impliqués pour livrer ces capacités. Si la bonne réponse ce trimestre est un arbre de comportement, c’est ce qui s’exécute. Si le trimestre prochain c’est un petit modèle embarqué, le fichier ne change pas.
Chaque frontière d’interface que vous tracez dans un système est un endroit où un changement futur peut se produire sans casser les appelants. Nous les avons tracées de façon agressive dès le départ. Nous en récoltons les économies maintenant.
Pourquoi cela compte de plus en plus, pas de moins
Trois raisons.
Un. Le marché des modèles n’arrête pas de bouger. Le meilleur modèle de l’an dernier est celui qui coûte cher cette année. Le meilleur de cette année sera dépassé l’an prochain. Les équipes qui ont câblé en dur un fournisseur de modèle dans leur moteur ont déjà fait cette intégration deux ou trois fois à ce stade. Les équipes qui ont mis une interface de gestion de modèles en travers l’ont fait une seule fois.
Deux. L’embarqué compte plus qu’avant. L’interface nous permet d’exécuter le même ai_behavior: "strafe" contre un modèle côté serveur en développement et un modèle embarqué en production. L’appelant ne le sait pas. Cette flexibilité est la seule raison pour laquelle l’inférence embarquée est envisageable sans réécrire la couche d’expérience.
Trois. Les assistants IA dans la boucle de développement bénéficient de frontières propres. Quand je demande à un assistant d’ajouter un nouveau comportement, le contrat qu’il doit respecter est l’interface de comportement enregistré. Pas un enchevêtrement de SDK fournisseurs. Plus l’interface est propre, plus vite l’assistant produit du code correct, et plus le coût de relecture de mon côté est faible.
Ce qui est difficile dans tout ça
Les coûts honnêtes.
La conception d’interface prend plus de temps que l’implémentation. Il est vraiment tentant de sauter l’étape d’interface et d’écrire directement la version qui marche. Résistez. Chaque raccourci que vous prenez ici, vous le payez plus tard quand vous devez échanger l’implémentation.
Vous devez être disciplinés sur ce qui entre dans l’interface. Chaque paramètre est un contrat que vous ne pouvez pas casser facilement. Ajoutez-en moins que vous ne pensez en avoir besoin. Attendez le second cas d’usage pour voir ce qui est réellement générique. La première version de l’interface IA avait cinq paramètres qui se sont révélés ne pas y avoir leur place. Les retirer plus tard a été douloureux.
Les comportements enregistrés ont besoin de versionnage. Quand "strafe" signifie une chose dans une version du runtime et une chose légèrement différente dans la suivante, les appelants le découvrent via des régressions de gameplay. Nous versionnons les comportements et épinglons les fichiers .raku à des versions de runtime. C’est agaçant. C’est nécessaire.
Parfois vous voulez réellement la dépendance. Voici la partie hérétique. Il existe des cas où un modèle spécifique a une capacité spécifique qu’aucune interface générique ne peut exprimer. Le geste honnête est d’étendre l’interface pour que la capacité devienne générique, pas de laisser fuir le modèle dans l’appelant. Nous nous sommes surpris à vouloir prendre le raccourci plus d’une fois.
L’argument architectural est simple. L’IA est une primitive du runtime. Le format de fichier qui la pilote nomme des capacités, pas des modèles. Le runtime résout les capacités vers quel que soit le sous-système qui les livre actuellement. Le fil entre les trois couches est l’interface. L’interface est petite, stable, et volontairement simpliste.
Voilà tout le billet.
Retour à la construction.
Apportez votre modèle. L'interface attend.
RakuAI parle à n'importe quel modèle via des poignées et des tenseurs — côté serveur en développement, embarqué en production, l'appelant ne voit jamais la différence. Découvrez comment vos poids se branchent sur un runtime spatial construit pour survivre au marché des modèles.