RTX 5090 et MacBook Air M4 : peut-on vraiment jouer ?
(scottjg.com)- Un eGPU RTX 5090 a été branché à un MacBook Air M4 pour lancer des jeux dans une VM Linux, en contournant l’absence de pilotes macOS et les limites de Thunderbolt
- L’implémentation a nécessité un périphérique PCI virtuel apple-dma-pci, un contournement du mappage DART, un patch du pilote NVIDIA basé sur kprobes, ainsi que des modifications de QEMU/HVF
- Cyberpunk 2077 atteint 27 fps en 4K RT Ultra sur un M4 Air + eGPU, et jusqu’à 111 fps avec la génération d’images DLSS, ce qui le rend jouable
- La même RTX 5090 installée dans un PC PCIe classique est 2 à 4 fois plus rapide selon les jeux, avec un important surcoût dû à FEX, Proton, la VM et Thunderbolt
- Le gain le plus marquant concerne l’inférence IA : le prefill de Qwen sur M4 Air est réduit d’environ 100x, et la génération en flux unique passe d’environ 22 tok/s à 155 tok/s
Les contraintes des eGPU Thunderbolt et des Mac Apple Silicon
-
Architecture d’un eGPU Thunderbolt
- Une carte graphique de bureau comme la RTX 5090 est insérée dans un dock Thunderbolt, qui convertit le PCIe en Thunderbolt avant connexion au port USB-C du MacBook Air M4
- Thunderbolt tunnele le PCIe via un câble USB-C, de sorte que pour l’ordinateur, le périphérique Thunderbolt apparaît comme un périphérique PCIe et non USB
- Thunderbolt 4 fournit jusqu’à 4 lignes PCIe à 40 Gbit/s, avec une légère perte de performances liée au tunneling
- En général, sous Linux et Windows, un eGPU est vu comme un périphérique PCIe un peu plus lent et fonctionne presque immédiatement avec les pilotes existants
- Le premier obstacle est que macOS sur Apple Silicon ne fournit pas nativement de pilotes NVIDIA ni AMD
-
Contournement via une VM Linux
- Il est possible d’exécuter Linux sur Apple Silicon Mac, mais le noyau Linux actuel ne prend pas en charge Thunderbolt sur Apple Silicon et ne gère que les périphériques internes et l’USB3
- En exécutant une VM Linux ARM64 sur un hôte macOS, on peut combiner la gestion des périphériques Thunderbolt par macOS avec la prise en charge du GPU NVIDIA par Linux
- Linux est utilisé faute de pilote NVIDIA pour Windows ARM64
- Le pilote eGPU macOS de tinygrad ne fonctionne qu’au sein de la pile tinygrad, donc il ne convenait pas comme pilote générique pour l’inférence IA ou l’exécution de jeux
Mise en œuvre du passthrough PCI sur macOS
-
PCI BAR et DMA
- Pour qu’une VM communique avec un périphérique PCI, les PCI BAR (Base Address Registers) et le DMA doivent fonctionner
- Les PCI BAR sont des zones mémoire qu’un périphérique PCI utilise pour lire et écrire sur l’ordinateur, et il faut les refléter dans la VM pour que le passthrough PCI fonctionne
- Le DMA (Direct Memory Access) permet à un périphérique de lire et écrire directement dans la mémoire de l’ordinateur sans copie par le CPU
-
Hypervisor.framework et mappage des BAR
- Au démarrage de la VM, QEMU appelle
Hypervisor.frameworkviahv_vm_map()pour configurer la disposition mémoire du guest - En se connectant au pilote hôte PCIDriverKit et en mappant la mémoire du périphérique PCI dans le processus via
IOConnectMapMemory64(), il devient possible de lire la mémoire BAR0 - La lecture de
BAR0[0]renvoie0x1b2000a1, une constante spécifique au périphérique correspondant à la RTX 5090 - Si QEMU mappe directement la mémoire du périphérique dans le guest, le guest peut communiquer directement avec le GPU, mais au début le noyau hôte plantait dès que la VM touchait la mémoire PCI BAR
- La cause était que QEMU ajoutait
HV_MEMORY_EXECmême aux mappages RAM-device/MMIO ; retirerEXECpour les mappages device/MMIO a permis d’éviter le panic du noyau hôte - Cette approche est environ 30 fois plus rapide que de piéger chaque accès, mais comme
hv_vm_map()ne permet pas de spécifier le subtype de mémoire de périphérique ARM, les écritures BAR restent environ 10 fois plus lentes que dans un cas normal
- Au démarrage de la VM, QEMU appelle
Contourner les limites du DMA et de DART
-
Les limites de DART sur Apple Silicon
- Apple Silicon intègre un composant matériel DART, proche d’un IOMMU, qui sert aussi de frontière de sécurité pour empêcher un périphérique d’accéder arbitrairement à la mémoire hôte
- Avec les périphériques Thunderbolt via PCIDriverKit, trois limites étaient particulièrement bloquantes
- Une limite de mappage d’environ 1,5 Go empêche de conserver tels quels les mappages mémoire de 8 à 16 Go requis par CUDA et les jeux modernes
- Un plafond d’environ 64k mappages sature la table si de nombreux petits buffers DMA sont utilisés
- Il n’est pas possible de choisir directement l’adresse et l’alignement, ce qui rend inadapté le modèle d’IOMMU virtuel où le guest décide des adresses DMA
- La méthode
restricted-dma-pool, qui force le DMA dans une région préallouée, fonctionnait pour des périphériques simples mais ne convenait pas à un pilote GPU
-
Le périphérique PCI virtuel
apple-dma-pci- Un périphérique PCI virtuel nommé
apple-dma-pcia été ajouté à QEMU puis injecté dans la VM aux côtés du GPU passé en passthrough - Un pilote du noyau guest intercepte les appels de mappage DMA du pilote NVIDIA, crée les mappages à la demande pour chaque requête DMA et les libère lorsque les buffers sont relâchés
- Avec cette architecture, il suffit que l’ensemble actif des buffers DMA du moment tienne sous la limite de 1,5 Go, au lieu de mapper toute la RAM du guest
- Le pilote guest remplace les opérations DMA du pilote NVIDIA par des opérations personnalisées avant toute utilisation du GPU, avec de fins wrappers pour
map_page,unmap_page,map_sg,unmap_sg,allocetfree - Côté hôte, QEMU transmet les requêtes au pilote PCIDriverKit, qui effectue le mappage DART réel puis réécrit l’adresse DMA dans la mémoire guest
- Un périphérique PCI virtuel nommé
-
Problème d’alignement NVIDIA et patch kprobes
- Lors de l’exécution de charges CUDA, les journaux noyau affichaient
NV_ERR_INVALID_OFFSETainsi que des messages liés à l’alignement deRM_PAGE_SIZE_HUGE - L’allocation problématique était une allocation de 16 Mo de type
UVM_RM_MEM_TYPE_SYS, et le pilote NVIDIA entrait en conflit avec les contraintes d’alignement en utilisant une taille de page de 2 Mo - Le pilote disposait d’un contournement qui réessayait avec la taille de page par défaut en cas d’échec
NV_ERR_NO_MEMORY, mais pas pourNV_ERR_INVALID_OFFSET - En utilisant les kprobes du noyau Linux, l’appel à
nvUvmInterfaceMemoryAllocSys()a été modifié pour forcerpageSizeàUVM_PAGE_SIZE_DEFAULT - Sans modifier directement le pilote NVIDIA, cela a permis de demander une taille de page plus petite et de faire fonctionner des charges simples
- Lors de l’exécution de charges CUDA, les journaux noyau affichaient
-
Le coalescing des mappages pour contourner la limite de 64k
- Lorsque les réglages graphiques montaient, de nombreux petits mappages apparaissaient et dépassaient la limite d’environ 64k mappages
- Les journaux montraient que plus de 90 % des mappages faisaient 4 kio et formaient des grappes, sans être forcément contigus
- La mémoire guest a été divisée en régions fixes de 256 kio, et lorsqu’un buffer de 4 kio devait être mappé, toute la grappe correspondante était mappée afin que les allocations suivantes dans la même zone réutilisent le mappage existant
- Cette méthode mappe un peu plus de mémoire que les seuls buffers réellement actifs, mais dans les charges testées elle restait sous le plafond d’environ 1,5 Go
- Le nombre de mappages actifs a diminué d’environ 4x, créant la marge nécessaire pour lancer des jeux exigeants avec les réglages les plus élevés
Amélioration des performances de la VM
-
Ordonnancement des vCPU
- Les scores de benchmark variaient fortement de manière aléatoire et tombaient parfois à 50 % de moins
- QEMU ne définissait pas de priorité pour les threads vCPU, ce qui donnait l’impression que l’ordonnanceur n’accordait pas assez de temps CPU à la VM
- Un patch a ajouté
pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0)etpthread_setschedparam(..., SCHED_RR, ...)au chemin de démarrage des vCPU de QEMU pour leur attribuer une priorité élevée
-
Total Store Ordering et FEX-Emu
- La VM exécute Linux arm64, mais la plupart des jeux distribués sont des binaires Windows x86-64 ; Proton) et FEX-Emu sont donc utilisés ensemble
- x86 applique le Total Store Ordering (TSO), où les écritures deviennent visibles des autres cœurs dans l’ordre du programme, alors qu’ARM utilise un modèle mémoire plus faible
- Apple Silicon dispose d’un mode matériel TSO par thread, exposé par
Hypervisor.frameworkà partir de macOS 15+ - Un patch repris de UTM active le bit 1 de
ACTLR_EL1sur les vCPU et désactive l’émulation logicielle du TSO dans FEX-Emu - Si l’émulation logicielle du TSO est désactivée sans activer ce bit matériel, Geekbench 6 plante en cours d’exécution
- Les performances guest avec FEX et le TSO CPU restent environ 50 % inférieures aux performances natives de l’hôte
Performances en jeu
-
Cyberpunk 2077
- Cyberpunk 2077 a été testé sur M4 Air macOS natif, M4 Air + eGPU, MacBook Pro Intel 2020 + eGPU, M5 Max macOS natif, M5 Max + eGPU, et un PC gaming i5-12600K + RTX 5090
+ Framegendésigne l’usage de DLSS 4x pour les configurations eGPU/PCIe natif, et de FSR 2x pour les configurations macOS natives- En 720p Low, la charge GPU était faible et le CPU ainsi que le surcoût de l’émulation/virtualisation dominaient ; le M4 Air natif à 61 fps dépassait ainsi le M4 Air + eGPU à 49 fps
- En 1080p High, le M5 Max natif sous macOS atteignait 131 fps contre 68 fps pour le M5 Max + eGPU, montrant que lorsque l’iGPU suffit, le surcoût prend davantage de poids
- Le M4 Air natif sous macOS tombait à 7 fps en 1080p RT Ultra, mais le M4 Air + eGPU atteignait 30 fps, et 119 fps avec génération d’images
- En 4K, le GPU devient le principal goulot d’étranglement : le M4 Air + eGPU affiche 27 fps en RT Ultra et 111 fps avec génération d’images DLSS, soit un niveau jouable
- Le PC gaming en PCIe natif était le plus rapide avec 100 fps en 4K RT Ultra et 282 fps avec génération d’images DLSS
- Le M5 Max + eGPU atteignait 47 fps en 4K RT Ultra et 145 fps avec génération d’images, soit environ 30 à 70 % de mieux que le M4 Air + eGPU
-
Autres jeux et benchmarks
- Dans GravityMark, connecter le même GPU en Thunderbolt au lieu d’un slot PCIe réduisait les performances d’environ 20 %, et le M4 Air + eGPU était environ 31 % plus lent qu’une connexion PCIe native
- Dans Shadow of the Tomb Raider, l’eGPU faisait passer le M4 Air de 8 fps natifs en 4K à 40 fps, et de 26 fps natifs en 1080p à 42 fps avec eGPU
- Les performances eGPU dans Shadow of the Tomb Raider étaient presque identiques en 1080p et en 4K, signe que le goulot d’étranglement n’était pas le GPU mais le CPU sous FEX
- Horizon Zero Dawn Remastered demandait plus de 1,5 Go de mappages mémoire DMA dès le benchmark en 720p minimum, ce qui empêchait son lancement
- Doom (2016) fonctionnait dans la configuration eGPU avec 49 fps selon l’overlay de performances et restait toujours au-dessus de 30 fps
- Crysis Remastered était jusqu’à 4 fois plus lent que sur le PC gaming, mais tournait malgré tout à un framerate jouable sur le MacBook Air M4
Performances en inférence IA
-
Qwen 3.6
- Le modèle Qwen 35B MoE a été testé en quantification 4 bits, avec 3B paramètres actifs
- Sur GPU NVIDIA, la configuration utilise vLLM et la version NVFP4 ; sur Apple Silicon, elle utilise vllm-mlx et un modèle MLX quantifié en 4 bits
- Les benchmarks ont été exécutés avec
llama-benchy - L’eGPU Thunderbolt perdait environ 9 % de performances face au PCIe, mais comme l’essentiel du traitement se faisait sur la carte, le résultat restait assez proche du PCIe
- La RTX 5090 était 6,5 fois plus rapide que le M4 Air en génération à flux unique, 2,1 fois plus rapide que le Mac Studio M4 Max, et 1,2 fois plus rapide que le MacBook Pro M5 Max
- Avec un prompt de 4K tokens, le M4 MacBook Air mettait 17 secondes pour le prefill, contre 150 ms avec eGPU sur la même machine, soit un gain de 120x
- En passant de 1 à 4 requêtes simultanées, le débit total de la configuration RTX 5090 augmentait d’environ 3x, tandis que les Mac Apple Silicon montaient moins bien en charge
-
Gemma 4
- Gemma 4 31B est un modèle dense 31B, et non un MoE sparse, donc chaque token traverse l’ensemble des paramètres
- L’iGPU du M4 Air ne dépassait pas 2 à 3 tokens par seconde pendant les tests, ce qui le plaçait hors d’une plage réellement utile
- Les configurations RTX 5090 sous vLLM se regroupaient toutes autour de 50 t/s à quelques pourcents près, tandis que le Mac Studio M4 Max atteignait environ 22 t/s et le MacBook Pro M5 Max environ 27 t/s
- En prefill également, la RTX 5090 restait toujours sous 400 ms, alors que le M4 Max mettait jusqu’à 21 secondes pour analyser un prompt de 4K tokens, et le M5 Max environ 7,5 secondes
- Les configurations vLLM atteignaient environ 3,5x le débit à 4 requêtes simultanées, tandis que le backend MLX sur Mac progressait à peine avec 4 requêtes
Exécution pratique et stabilité
-
Déploiement et conditions de build
- En l’état, ce n’est pas encore quelque chose qu’on peut “télécharger et lancer” directement, car un entitlement spécial d’Apple est nécessaire
- Une demande d’entitlement a été faite, mais aucune réponse n’a encore été reçue, et l’attente peut durer plusieurs mois
- En attendant, il est possible de compiler soi-même le pilote, à condition que ce Mac figure dans le compte associé au certificat de signature
- Il n’est pas nécessaire de désactiver SIP ni d’utiliser le reduced security mode
- Le code est disponible sur qemu-vfio-apple
- Le lanceur inclus télécharge automatiquement une image Ubuntu précompilée avec le pilote spécial
apple_dmainstallé
-
Stabilité
- La stabilité n’est pas encore bonne
- FEX souffre actuellement d’un bug provoquant des plantages fréquents de Steam en boucle, et le problème semble encore plus marqué dans cette configuration
- Le lancement de certains jeux peut prendre plusieurs minutes
- À cause de la limite de mappage DMA, les mappages peuvent se fragmenter avec le temps et il peut finir par manquer d’espace pour lancer un nouveau jeu
- Dans ce cas, il faut arrêter la VM Linux, débrancher puis rebrancher le GPU afin d’effacer tous les mappages DMA, puis réessayer
- La charge de travail la plus stable reste l’IA, pour laquelle cette solution fonctionne très bien
- L’intégration des patches dans QEMU upstream est en cours, avec pour objectif idéal une inclusion dans une distribution QEMU grand public comme UTM, afin que cela fonctionne directement
1 commentaires
Avis sur Hacker News
Cela fait des années que je demande à l’équipe VM le passthrough GPU pour VM. J’ai travaillé sur un Mac Pro Apple Silicon, et cela aurait eu bien plus de sens si on avait pu faire du passthrough d’un GPU placé dans le boîtier vers une VM Linux
Dommage que la demande n’ait pas été retenue, mais c’est cool que d’autres aient réussi à le faire fonctionner
Au final, on dirait surtout qu’un moniteur de machine virtuelle comme QEMU devrait adopter cette interface en plus d’un mécanisme du type Linux VFIO. Si on parle de Virtualization.framework, c’est déjà pratiquement une sorte de moniteur de VM en soi, donc je me demande précisément ce qu’il manquerait à macOS
Récemment, des patchs sont aussi arrivés dans le mainline de QEMU pour prendre en charge quelque chose de similaire dans des invités Linux sous Hypervisor.framework, en utilisant un « venus server » avec virtio-gpu. Ensuite, Apple semble avoir en interne un support du passthrough PCI pour Virtualization.framework. Le code du framework lui-même est livré aux clients, mais il semble dépendre de kexts ou de composants noyau non inclus dans macOS standard. Je ne sais pas s’ils comptent le publier pour les clients, mais il est clair que quelqu’un chez Apple a réfléchi à cette fonctionnalité
De toute façon, le Mac Pro est maintenant un produit mort. Les ventes aux seuls professionnels de l’audio et de la vidéo ont leurs limites
Excellent article. Les benchmarks gaming sont amusants, mais d’un point de vue pratique, la partie vraiment intéressante est l’amélioration des performances LLM
La plateforme Apple est accessible et pratique pour faire tourner des modèles en local grâce à sa grande quantité de RAM, mais sa vitesse de traitement des prompts, relativement lente, est souvent négligée. Comme le cite l’article, sur un prompt de 4K tokens, un MacBook Air M4 met 17 secondes à parser avant de commencer à générer une réponse, alors qu’avec un eGPU, cela prend 150 ms, soit 120 fois plus rapide. Quand on bricole avec des LLM sur de petits chats, le problème du prefill est peu visible, mais dès qu’on passe à des tâches plus importantes, la limite de calcul devient le goulet d’étranglement. Même le graphique du temps jusqu’au premier token (TTFT) ne semblait pas si mauvais, jusqu’à ce qu’on réalise qu’il avait fallu le tracer en échelle logarithmique parce que la plateforme Mac était bien trop lente par rapport à la puissance GPU totale
Quand l’auteur présente les résultats de cette manière, cela peut donner une impression de biais, mais je pense que ce n’est probablement pas le cas en réalité
Il semble exact que le jeu soit totalement injouable, même via CrossOver, parce qu’OpenGL n’est plus vraiment bien pris en charge sur macOS
Cela dit, Doom semble prendre en charge Vulkan, et il serait peut-être possible d’ajouter
VK_NV_glsl_shaderà MoltenVK. Ce serait bien moins de travail que d’attacher une RTX 5090 à un M4, mais bravo quand même à Scott. Les vitesses d’inférence IA en local sont aussi assez impressionnantes, et c’est vraiment un projet de dingueC’est assez impressionnant. J’avais l’impression que, sur Apple Silicon, les eGPU ne fonctionnaient tout simplement pas
Apple dit la même chose : « Pour utiliser un eGPU, vous avez besoin d’un Mac équipé d’un processeur Intel. » En plus, les eGPU officiellement pris en charge étaient tous en AMD, pas en NVIDIA. https://support.apple.com/en-us/102363
Ici, l’idée est de tunneler cet eGPU vers une VM Linux
Au début, je pensais que ce serait un article sur une VM exécutée avec le pilote tinygrad lent, mais l’approche était en fait bien meilleure
C’est dommage qu’Apple ne le prenne pas mieux en charge et n’assouplisse pas la limite de fenêtre à 1,5 Go : cela rendrait les choses beaucoup plus simples. Arm a globalement quelques particularités autour des périphériques PCIe, mais au moins sous Linux, c’est devenu bien plus facile puisque la plupart des pilotes récents traitent arm64 comme une plateforme de premier rang
Mon intuition est que tinygrad est surtout lent parce que son moteur d’inférence n’est pas encore très optimisé pour ce genre de modèles LLM publics. J’imagine qu’une grande partie du travail a plutôt été consacrée à l’optimisation de la pile de George pour son entreprise de matériel de conduite autonome. Comme on ne peut pas simplement réutiliser les kernels CUDA existants dans ce moteur, la difficulté d’ingénierie est bien plus élevée. Je me demande aussi si mon projet pourrait partager le pilote hôte macOS avec le leur. Il faudrait sans doute quelques modifications, mais il semble y avoir pas mal de recouvrement
Le passage disant que « la première étape de la plupart des projets, ces temps-ci, c’est de demander à une IA, même si on ne veut pas l’admettre » signifie plus probablement que l’IA va vous dire ce qu’elle ne connaît pas elle-même
Ça m’a rappelé une discussion d’hier avec ChatGPT sur le fait de savoir si la 5070TI était une vraie carte graphique. ChatGPT n’arrêtait pas d’essayer de me corriger en disant qu’aucune 5070TI n’existait et que je voulais sûrement parler de la 4070ti
J’ai demandé à Claude de créer une page HTML sur PowerShell 7, et il a écrit que 7.4 était la dernière version LTS. Je lui ai donné un lien indiquant que 7.6 était sortie en mars et je lui ai demandé de refaire la page avec les informations à jour, mais il a produit quasiment la même page en répétant la même affirmation selon laquelle 7.4 était la dernière version
Cela dit, la réponse de ChatGPT sur l’article d’origine était tout à fait juste. Le résumé en mode « très profond », « presque impraticable » et « du point de vue de la recherche » décrit parfaitement cet article lui-même
C’est vraiment impressionnant. J’ai une 5090 qui traîne et je fais tourner claw-like sur un M4 Mini ; si je pouvais l’insérer dans une sorte de cadre imprimé en 3D et la brancher sur le port TB pour la stabilité, cela pourrait devenir un outil assez utile pour l’inférence locale
Il faudrait tout de même un dispositif garantissant proprement l’alimentation. Le problème, c’est que
max-num-seqsetmax-model-lenentrent en conflit, et qu’en dehors d’un pur mode mono-client, il faut en quelque sorte plusieurs slotsCela semble potentiellement très utile pour l’inférence IA, à condition de passer l’approbation d’Apple. J’avais envie d’utiliser un GPU NVIDIA avec un Mac Mini, et avec cette méthode on peut faire tourner CUDA directement. Vraiment génial