- Moteur d’inférence en C/Metal capable d’exécuter un modèle Mixture-of-Experts de 397B de paramètres sur un MacBook Pro (48 Go de RAM) à plus de 4,4 tokens/s
- Le modèle complet de 209 Go est streamé depuis le SSD, avec une implémentation uniquement en C et shaders Metal, sans Python ni framework
- SSD Expert Streaming, noyaux optimisés FMA, Deferred GPU Compute, etc. maximisent l’efficacité parallèle GPU·SSD·CPU
- La configuration en quantification 4-bit offre un bon équilibre entre qualité et vitesse, avec une génération de sortie de niveau production incluant la fonction d’appel d’outils
- Un exemple d’allégement et d’optimisation qui rend possible l’inférence en temps réel de très grands modèles MoE même sur un ordinateur portable
Résultats de performance
- En configuration experts 4-bit (noyau FMA) : 4,36 tok/s, excellente qualité, 209 Go utilisés sur disque
- La configuration 4-bit de base atteint 3,90 tok/s, avant optimisation FMA
- La configuration experts 2-bit (trust OS) monte à 5,74 tok/s, plus rapide mais inutilisable pour les appels d’outils à cause d’erreurs de sortie JSON
- Le pic 2-bit sur un token unique atteint jusqu’à 7,05 tok/s, mais reste inadapté à un usage réel
- La quantification 4-bit est la configuration la plus adaptée à une exploitation réelle
Environnement matériel
- MacBook Pro (Apple M3 Max), CPU 16 cœurs (12P+4E), GPU 40 cœurs, ANE 16 cœurs
- 48 Go de mémoire unifiée, bande passante d’environ 400 Go/s
- SSD Apple Fabric de 1 To, avec une vitesse de lecture séquentielle de 17,5 Go/s
- Environnement macOS 26.2 (Darwin 25.2.0)
Architecture du modèle
- 60 couches Transformer au total : 45 GatedDeltaNet (attention linéaire) + 15 attention complète
- Chaque couche possède 512 experts, avec K=4 activés par token (dont 1 expert partagé)
- Dimension cachée 4096
-
Technologies clés
-
SSD Expert Streaming
- Les poids des experts (209 Go en 4-bit) sont chargés à la demande depuis le SSD NVMe via des
pread() parallèles
- À chaque couche, seuls les 4 experts activés sont chargés (environ 6,75 Mo chacun)
- Le cache de pages de l’OS gère automatiquement la mise en cache, sans cache séparé nécessaire
- Architecture inspirée de l’article d’Apple « LLM in a Flash »
-
Noyau de déquantification optimisé FMA
- L’opération
(nibble * scale + bias) * x est réorganisée sous la forme fma(nibble, scale*x, bias*x)
scale*x et bias*x sont précalculés afin que les unités FMA du GPU l’exécutent en une seule instruction
- 12 % de gain de vitesse par rapport à une implémentation simple
-
Shaders de calcul Metal
- Les produits matrice-vecteur avec déquantification 4-bit/2-bit, l’activation SwiGLU, la normalisation RMS, l’attention GPU (Q@Kᵀ, softmax, scores@V), RoPE, ainsi que la combinaison MoE + résiduel + gate sont implémentés via des noyaux Metal écrits à la main
-
Deferred GPU Expert Compute
- Les commandes CMD3 (passe avant des experts) sont soumises de manière asynchrone afin que le CPU prépare la couche suivante pendant que le GPU exécute
- Les opérations de combinaison + normalisation + résiduel sont aussi effectuées sur le GPU puis transmises directement à la couche suivante
-
Utilisation d’Accelerate BLAS
cblas_sscal, cblas_sgemv, cblas_sger sont utilisés pour les calculs récurrents de GatedDeltaNet
- 64 % plus rapide qu’un code scalaire
-
Trust the OS
- Suppression du cache personnalisé, le cache de pages de l’OS (basé sur LRU, environ 35 Go) prend en charge la mise en cache des données d’experts
- Plus rapide que leur propre LRU Metal, le cache
malloc ou le cache compressé LZ4
- Taux naturel de hit cache de 71 %
-
Contraintes de la mémoire unifiée
- Sur Apple Silicon, le DMA du SSD et les calculs GPU partagent le même contrôleur mémoire
- En exécution parallèle, la saturation de la bande passante GPU entraîne une forte hausse de la latence
- Le pipeline séquentiel GPU → SSD → GPU est la forme optimale pour ce matériel
1 commentaires
Commentaires sur Hacker News
Il existe une autre façon de faire tourner Qwen 3.5 397B même sur un appareil grand public
Il y a une version en quantification 2.5 BPW (quant) qui reste tout à fait exploitable sur une machine avec 128 Go de mémoire
Je l’ai fait tourner correctement sur un M1 Ultra à environ 20 tok/s, tout en conservant un contexte de 256k
Les résultats lm-evaluation-harness donnaient environ mmlu 87,86 %, gpqa diamond 82,32 %, gsm8k 86,43 %, ifeval 75,90 %
J’ai détaillé mon retour d’expérience dans le fil de discussion Hugging Face 1 et le fil 2
C’est un excellent modèle pour l’inférence hors ligne
Le nombre d’experts par token a été réduit de 10 à 4, ce qui dégrade encore davantage la qualité
C’est acceptable sur des prompts courts, mais inutilisable sur des sessions longues
Il y a aussi un problème où la sortie JSON génère
"name"au lieu de\name\, ce qui rend les appels d’outils instablesJ’aimerais aussi savoir si ça tient bien avec un long contexte
Et ça fait plaisir de revoir ce pseudo après tout ce temps — la personne qui a créé Neovim, quel succès impressionnant
Je l’utilise moi aussi tous les jours
Peut-être qu’on pourrait l’estimer avec CoconutBattery
En regardant les détails, on dirait qu’ils obtiennent environ 5 tok/s avec une quantification 2-bit et en réduisant le nombre d’experts de 10 à 4
C’est une preuve de concept intéressante, mais on est loin de la qualité du modèle 397B d’origine
Ce genre d’optimisation extrême entraîne une perte d’intelligence du modèle
Il y a aussi le problème où la sortie JSON génère
"name"au lieu de\name\, donc ce n’est pas adapté à un usage réelJe reconnais que cela montre que ce type d’expérience est techniquement possible, mais ce n’est pas vraiment utilisable en pratique
En ce moment, je ressens une vraie fatigue face à tous ces articles écrits par l’IA
Mais j’ai entendu dire qu’en pratique, même des LLM commerciaux ont une précision médiocre pour les appels d’outils
Il doit y avoir une difficulté d’implémentation, ou quelque chose de structurellement impossible
Il y a aussi eu une discussion liée sur r/LocalLLaMA
D’après la page GitHub, un simple accès par mmap finit limité par le surcoût par page
Je me demande si on pourrait améliorer ça sur macOS en configurant des huge pages ou en faisant du préchargement avec
posix_fadviseLa dégradation de qualité en quantification 2-bit est réellement un problème sérieux
Dans des tâches réelles, j’ai souvent constaté qu’un modèle 30B 4-bit bien ajusté vaut mieux qu’un 70B+ en 2-bit
Réduire le nombre d’experts revient en fait à obtenir un autre modèle
Cela dit, c’est intéressant de pousser le matériel grand public dans ses retranchements
Le titre « faire tourner ça sur un ordinateur portable » finit toujours par vouloir dire un MacBook à 3 000 $, et c’est lassant
Les techniques de compression sont impressionnantes, mais ce n’est pas une option réaliste pour l’utilisateur moyen
Mais en lisant le titre, je ne m’attends pas à ce que ça tourne sur n’importe quel portable
Plutôt que d’être trop cynique, je préfère apprécier ce type d’expérimentation
Beaucoup de gens possèdent déjà ce genre de MacBook haut de gamme pour le montage vidéo ou d’autres usages
Le principal avantage, c’est de pouvoir expérimenter sur son portable existant sans matériel supplémentaire
J’obtenais environ 20 tok/s, et je pense que ça reste tout à fait accessible pour un particulier
Pour un usage pro aussi, l’investissement en valait la peine
« No Python. No frameworks. Just C, Objective-C, and hand-tuned Metal shaders. »
En lisant cette phrase, j’ai tout de suite compris d’où venaient les tokens
« Hand-written Metal kernels » : serait-ce par hasard écrit directement par GPT ? 😉
C’est vraiment un résultat impressionnant
Je me demande si, sous Linux aussi, on pourrait utiliser une méthode similaire avec un accès basé sur la mémoire système au lieu du SSD
Ou bien l’idée de stocker les poids sous forme de ROM est également intéressante
C’est juste que ce projet utilise Metal, donc il reste spécifique à macOS
Mais les modèles MoE restent malgré tout fortement limités par la bande passante
Mais à l’étape de décodage, le surcoût de transfert CPU est plus important qu’avec le GPU, donc le gain est faible
Il est plus efficace d’utiliser le GPU uniquement pour accélérer les parties partagées
Mais dès que le modèle bascule vers la RAM système ou le disque, les performances chutent brutalement
Il était expliqué que le SSD était le goulot d’étranglement, mais l’auteur dit avoir obtenu 15 Go/s
Pourtant, de ce que je savais, la bande passante max était plutôt autour de 8 Go/s. Qu’est-ce que j’ai raté ?
Une fois le résultat du routeur obtenu, on charge les experts depuis le SSD, et c’est à ce moment-là que le SSD sature
L’I/O moyenne est d’environ 2970 MB/s, donc bien en dessous de la limite du SSD
On pourrait peut-être faire mieux en parallélisant certains tenseurs avec des lectures asynchrones
Quand j’ai expérimenté sous Linux avec io_uring, environ 20 % des lectures se terminaient en parallèle du calcul