mllm : un moteur d'inférence multimodal pour téléphones et cartes embarquées
Fast Multimodal LLM on Mobile Devices
En bref
- De quoi s’agit-il ?
- mllm est un moteur d'inférence C++ sous licence MIT qui vise le multimodal sur CPU Arm, GPU OpenCL, NPU Hexagon et NPU Ascend. Sa promesse est double : un chemin de conversion depuis PyTorch et SafeTensors, et une couche de service locale côté Android. La partie serveur Jetson, elle, dépend d'un second paquet Python qui n'est pas le moteur C++.
- À qui s’adresse-t-il ?
- Adoptez mllm si votre cible est un appareil Arm ou un NPU Hexagon et que vous acceptez de passer par mllm-convertor pour produire des poids au format du projet. Évitez-le si vous avez besoin d'une API stable : la branche main est passée en V2, la V1 est en fin de support, et la documentation ne décrit pas de politique de compatibilité entre les deux.
- Puis-je l’utiliser commercialement ?
- Oui. MIT est une licence permissive : vous pouvez utiliser, modifier et vendre un logiciel qui en dépend, à condition de conserver les mentions de droit d’auteur et de licence.
- Est-il encore maintenu ?
- Oui. Les derniers commits datent d’il y a 7 jours.
- En quel langage est-il écrit ?
- Principalement C++, d’après les statistiques de langage de GitHub.
Ces réponses reposent sur les données GitHub du projet (dernière synchronisation le 15 septembre 2026) et sur notre analyse. Elles ne constituent pas un avis juridique.
ANALYSE OPEN SOURCE APPROFONDIE
Le problème visé : faire tenir un modèle multimodal sur l'appareil
Un modèle de vision-langage classique suppose un serveur, une carte graphique et un client réseau. mllm prend le problème dans l'autre sens : le moteur est écrit en C++, compilé pour l'appareil, et le README le présente comme un moteur d'inférence multimodal léger destiné au mobile et à l'edge. Le public visé est donc celui qui doit faire tourner Qwen3, Qwen3-VL ou MiniCPM5-2B sur un téléphone ou une carte embarquée, sans envoyer l'image de l'utilisateur à un service distant. Le cas d'usage typique est une application Android ou un module Jetson. La documentation cite explicitement MiniCPM5-2B en inférence CPU Arm sur téléphone, et une démonstration Android avec streaming Qwen3 et DeepSeek-OCR. Ce n'est pas un projet de recherche sur les architectures de modèles : c'est de l'outillage de déploiement, avec un convertisseur, un runtime et un outil CLI.
Du checkpoint PyTorch au runtime : le rôle de mllm-convertor
Le flux de données est décrit dans le README. mllm-convertor ingère directement des modèles PyTorch et SafeTensors, applique la quantification, et écrit des poids au format mllm. Le runtime mllm charge ensuite ces fichiers et les exécute. Entre les deux, le README situe mllm comme un point de passage entre les algorithmes d'optimisation (décodage spéculatif, élagage, quantification) et les couches compilateur/runtime matérielles, avec CANN, CUDA et MLIR cités comme exemples. Cette position centrale est une affirmation d'architecture, pas un résultat mesuré. Ce qui est vérifiable dans le dépôt, c'est la présence d'un dossier examples par modèle, et un tableau de modèles pris en charge qui associe à chaque modèle un backend et un format de quantification : Qwen3-0.6B en w4a8 sur CPU et en W8A8 sur Ascend, Qwen3-1.7B en W4A16 sur Hexagon pour SM8650. Autrement dit, le chemin de conversion n'est pas neutre : le format de quantification dépend du backend que vous ciblez, et les poids convertis publiés sur ModelScope sont liés à un couple modèle plus matériel.
Deux piles dans un même dépôt : le moteur C++ et pymllm
Il faut distinguer ce que le dépôt contient réellement. D'un côté, le moteur C++ avec ses backends Arm CPU, OpenCL, QNN et Ascend, et un outil CLI d'inférence. De l'autre, pymllm, un paquet Python qui sert de couche de service et qui porte les performances mises en avant sur Jetson Orin. Les chiffres publiés dans le README concernent pymllm : pour input_len=2048 et output_len=128, le README annonce jusqu'à 3,12x de gain en prefill pour Qwen3-VL-2B W8A8 sur AGX Orin 32GB, et environ 12243 tok/s de débit de prefill. Le même README précise que le débit de decode reste proche de llama.cpp, avec de petits gains ou pertes selon le modèle, l'appareil et la quantification. Ces mesures proviennent du projet, pas d'une évaluation indépendante, et la formulation elle-même invite à ne pas en tirer une règle générale. Le tableau multimodal donne, pour le chemin complet vision plus prefill image/texte, des valeurs moyennes sur runs répétés : 6443,59 tok/s en W8A8 contre 4875,75 en FP16 pour Qwen3-VL-2B sur AGX Orin 32GB. Le W4A16 y est légèrement en retrait du FP16 sur ce même couple, ce qui montre que la quantification n'est pas un gain automatique sur le prefill multimodal.
Côté Android, une architecture client-serveur embarquée
La démonstration Android ne passe pas par une intégration JNI classique. Le README décrit une couche serveur dans l'application, écrite en Golang et livrée sous forme de mllm_server.aar, qui découple l'interface utilisateur du calcul d'inférence. Le client et le serveur tournent tous les deux sur l'appareil. Ce choix a une conséquence pratique : le chemin de communication entre l'UI et le moteur devient un composant à part entière, avec son propre cycle de vie, au lieu d'être un appel de fonction. Le projet présente cette refonte comme le support d'un streaming stable pour Qwen3 et DeepSeek-OCR sur Android. Le README ne détaille pas le protocole utilisé entre les deux, ni la surcharge introduite par ce passage. C'est un point à examiner dans le code avant de bâtir une application dessus, d'autant que la couche Golang ajoute une chaîne de compilation supplémentaire à votre projet Android.
Mise en route : ce que la documentation donne
Le README renvoie à une section Quick Start et à un dossier examples organisé par modèle, par exemple ./examples/minicpm5/README.md pour MiniCPM5-2B. Il ne fournit pas, dans l'extrait disponible, la commande exacte de compilation ni les clés de configuration du runtime. Sur la partie Jetson, le point d'entrée identifié est le script bench_one_batch, avec une option --image pour mesurer le chemin multimodal complet, vision plus prefill image/texte. Les poids quantifiés sont publiés sur ModelScope et Hugging Face, avec des noms qui encodent le format : Qwen3-1.7B-Qnn-AOT-SM8650 pour le chemin Hexagon AOT, Qwen3-0.6B-W8A8-Ascend pour Ascend. La marche à suivre réaliste est donc : choisir un couple modèle et backend présent dans le tableau, récupérer les poids convertis correspondants, puis suivre la page Quick Start du site de documentation. Toute commande absente de cet extrait doit être prise sur le site, pas devinée.
Le coût réel : une transition V1 vers V2 en cours
Le rythme de publication est irrégulier. La version 1.0.0 date de janvier 2024, la 2.0.0 de février 2026, avec un dernier push en septembre 2026. Entre les deux, le projet a annoncé la fin prochaine du support de MLLM V1, avec une dernière intégration de GPT-OSS avant bascule vers V2. Le README liste ce que V2 change : une écriture de modèles plus Pythonique en exécution eager, un support de compilation pour l'intégration NPU, l'exécution parallèle de plusieurs modèles, et une réécriture de l'ingénierie. Pour un adoptant, cela signifie que le code V1 et le code V2 ne sont pas interchangeables, et que la documentation disponible couvre les deux époques. La licence MIT est permissive : elle autorise la modification et la redistribution, y compris dans un produit propriétaire, à condition de conserver l'avis de copyright. Elle ne couvre évidemment pas les licences des poids de modèles que vous convertissez, qui dépendent de chaque modèle téléchargé.
Quand mllm n'est pas le bon outil
Si votre cible est un serveur x86 avec GPU, mllm n'apporte rien que vous ne trouviez ailleurs, et son positionnement mobile se traduit par des formats de poids spécifiques et un convertisseur à faire tourner. Si votre modèle n'apparaît pas dans le tableau des modèles pris en charge, vous devrez écrire vous-même le chemin de conversion, et rien dans le README n'indique à quel point cette étape est outillée. Le support CUDA sur Jetson Orin et Jetson Thor est décrit comme expérimental et en développement actif, donc à éviter en production. Enfin, le support de la V1 s'achève : un projet démarré aujourd'hui sur V1 s'engage sur une branche en fin de vie. Un concurrent direct cité par le projet lui-même est llama.cpp, avec lequel pymllm est comparé sur le débit de decode. La différence d'approche tient au périmètre : llama.cpp est centré sur l'inférence de modèles de langage, tandis que mllm vise le multimodal et une abstraction de backends NPU, avec QNN et Ascend en plus du CPU et du GPU. Si votre besoin est du texte seul sur CPU, la comparaison du README indique elle-même que l'écart de decode est faible, ce qui rend l'argument en faveur de mllm plus difficile à défendre.
Conclusion éditoriale
Adoptez mllm si votre cible est un appareil Arm ou un NPU Hexagon et que vous acceptez de passer par mllm-convertor pour produire des poids au format du projet. Évitez-le si vous avez besoin d'une API stable : la branche main est passée en V2, la V1 est en fin de support, et la documentation ne décrit pas de politique de compatibilité entre les deux. Avant de vous engager, vérifiez trois choses concrètes : que le modèle que vous visez figure dans le tableau des modèles pris en charge pour votre backend, que les poids quantifiés existent déjà sur ModelScope ou Hugging Face (par exemple la variante Qwen3-1.7B-Qnn-AOT-SM8650), et que la licence MIT couvre bien l'usage commercial que vous prévoyez pour le binaire compilé.
Notes de la communauté