Configuration pour exécuter sans interruption un modèle MoE de 744B sur un Mac à 32 Go de RAM et sur une station de travail
Lorsque vous exécutez localement un modèle MoE de 744B tel que GLM-5.2 sur une station de travail standard ou un MacBook, le goulet d'étranglement se manifeste d'abord au niveau de la bande passante du stockage et de la politique de mémoire virtuelle du système d'exploitation, bien avant la capacité de la VRAM. colibrì, un moteur d'inférence léger écrit en C de JustVugg, maintient uniquement les couches résidentes de 17B (9,9 Go en int4) en RAM, tandis qu'il diffuse en temps réel par token les poids des experts de routage (~370 Go), divisés en 21 504 parties, depuis un SSD externe.
Le problème est que si vous l'exécutez avec les paramètres par défaut du système d'exploitation, des problèmes de thrashing du swap surviendront ou le tueur OOM (Out Of Memory) du noyau mettra fin de force au processus. Vous devez ajuster manuellement les indicateurs de compilation, les files d'attente d'E/S NVMe et les paramètres du noyau pour que la génération de jetons ne s'arrête pas.
Indicateurs de compilation par architecture et configuration du jeu d'instructions vectorielles
Le moteur de calcul principal de colibrì est un fichier unique en C11 d'environ 1 300 lignes sans dépendance externe. Le débit des calculs de multiplication matricielle varie selon les instructions vectorielles SIMD utilisées par le compilateur. Au lieu d'une construction par défaut -O2, vous devez spécifier explicitement les instructions d'extension arithmétique entière du processeur cible.
Mise en place de l'environnement de build et compilation
Pour l'environnement Linux x86_64, activez AVX-512 VNNI avec GCC 12 ou supérieur, et connectez NEON DotProduct via Clang sur Apple Silicon.
Installez d'abord les outils de build pour les distributions Ubuntu et Debian :
`bash
sudo apt-get install gcc-12 libomp-dev
`
Récupérez le runtime OpenMP via Homebrew dans l'environnement macOS :
`bash
brew install libomp
`
Compilez pour les instructions AVX-512 VNNI sur une machine Linux x86_64 :
`bash
gcc -O3 -march=native -mtune=native
-mavx512f -mavx512bw -mavx512vnni -mavx512dq
-fopenmp -funroll-loops -ffast-math
-o colibri_engine colibri_glm52.c -lm
`
Compilez en liant directement le chemin de la bibliothèque dans l'environnement Apple Silicon (série M) :
`bash
clang -O3 -mcpu=native
-march=armv8.4-a+dotprod+fp16
-Xpreprocessor -fopenmp
-I/opt/homebrew/opt/libomp/include
-L/opt/homebrew/opt/libomp/lib -lomp
-o colibri_engine colibri_glm52.c -lm
`
Si des avertissements de compilation liés aux structures apparaissent, ajoutez l'indicateur -std=c11 pour les résoudre.
| Plateforme |
Compilateur |
Indicateurs d'instructions vectorielles |
Parallélisation des threads |
| x86_64 (Linux) |
GCC 12+ |
-mavx512vnni -mavx512bw |
-fopenmp |
| ARM64 (macOS) |
Apple Clang |
-march=armv8.4-a+dotprod |
-Xpreprocessor -fopenmp |
| x86 ancien |
GCC / Clang |
-mavx2 -mfma |
-fopenmp |
Benchmark du SSD externe et extension de la file d'attente d'E/S asynchrone
La latence de décodage de colibrì est déterminée par la vitesse de lecture aléatoire par blocs de 1 Mo du disque. Les fichiers de paramètres des experts de routage de GLM-5.2 sont divisés en tailles d'environ 19 Mo chacun, et à chaque génération de jeton, le thread d'E/S asynchrone lit les blocs nécessaires depuis le SSD.
Mesure de la bande passante de lecture du stockage via fio
Installez fio à l'aide du gestionnaire de paquets :
`bash
Linux
sudo apt-get install fio
macOS
brew install fio
`
Mesurez la bande passante de lecture aléatoire avec une taille de bloc de 1 Mo et des conditions d'E/S directes (Direct I/O) :
`bash
fio --name=colibri_stream_bench
--filename=/Volumes/ExternalSSD/glm52_test.tmp
--size=10G
--rw=randread
--bs=1m
--iodepth=32
--numjobs=4
--ioengine=posixaio
--direct=1
--group_reporting
--runtime=60
`
Les interfaces Thunderbolt 4 ou USB4 (40 Gbit/s) maintiennent une bande passante mesurée de 2 800 à 3 200 Mo/s, atteignant une vitesse de décodage de 0,08 à 0,10 tok/s. En revanche, si vous connectez le lecteur externe à un port USB 3.2 Gen2 (10 Gbit/s) dont la bande passante se limite à 900-1 050 Mo/s, il faudra plus de 80 minutes pour générer 100 jetons.
`
+-------------------------------------------------------------------------+
| colibrì C Inference Engine |
| +--------------------------+ +-------------------------------------+ |
| | Dense Weights (9.9 GiB) | | Multi-Token Prediction (MTP) Head |
| | Resident in RAM (mlock) | | int8 Quantized |
| +------------+-------------+ +------------------+------------------+ |
+---------------+-----------------------------------+---------------------+
| |
v v
+-------------------------------------------------------------------------+
| Memory & Storage I/O Layer |
| +--------------------------+ +-------------------------------------+ |
| | Async Expert Readahead | | 21,504 Routed Experts (370 GB) |
| | I/O Queue Depth 3264 | | Streamed from NVMe SSD via mmap |
| +--------------------------+ +-------------------------------------+ |
+-------------------------------------------------------------------------+
`
Augmenter la profondeur de la file d'attente d'E/S (iodepth) de la pré-extraction asynchrone (Async Expert Readahead) à un niveau de 32 à 64 permet d'exploiter pleinement tous les canaux parallèles du contrôleur NVMe et de réduire la latence d'attente du disque.
Paramètres de mémoire virtuelle du noyau et de prévention OOM
Sur un système doté de 32 Go de RAM, lorsque les poids résidents de 9,9 Go, le cache KV et le cache de pages du système de fichiers sont chargés simultanément, des conflits de mémoire du noyau surviennent. Dans les paramètres Linux par défaut, l'allocation d'espace d'adressage virtuel lors d'un appel mmap de 370 Go est bloquée ou le tueur OOM arrête le moteur.
Application des paramètres du noyau Linux
Créez le fichier /etc/sysctl.d/99-colibri-memory.conf et saisissez-y le contenu suivant :
`ini
Empêcher le rejet des mémoires résidentes vers le swap du disque
vm.swappiness = 1
Autoriser le sur-engagement de la mémoire virtuelle (overcommit)
vm.overcommit_memory = 1
Assurer un espace mémoire tampon d'urgence (2 Go)
vm.min_free_kbytes = 2097152
Augmenter le taux de rétention du cache des fichiers sur disque
vm.vfs_cache_pressure = 50
Étendre la limite du nombre de correspondances mmap
vm.max_map_count = 524288
`
Appliquez immédiatement les paramètres après l'enregistrement :
`bash
sudo sysctl --system
`
Supprimez la limite de verrouillage de la mémoire dans la session du shell pour autoriser les appels mlock() de la couche résidente :
`bash
ulimit -l unlimited
`
| Paramètre |
Valeur par défaut |
Valeur recommandée |
Objectif du paramètre |
| vm.swappiness |
60 |
1 |
Empêcher les poids de 9,9 Go fixés en RAM d'être repoussés vers la zone de swap |
| vm.overcommit_memory |
0 |
1 |
Empêcher le refus de mémoire du noyau lors du mappage mmap de 370 Go |
| vm.vfs_cache_pressure |
100 |
50 |
Prolonger la durée de vie du cache de fichiers pour réutiliser les poids des experts appelés de manière répétée |
| vm.max_map_count |
65530 |
524288 |
Lever la limite de mappage pour 21 504 fichiers de paramètres |
| ulimit -l |
64 (Ko) |
unlimited |
Accorder l'autorisation de fixer les couches denses en mémoire physique (mlock) |
Dans l'environnement macOS, si la mémoire disponible s'épuise, le démon dynamic_pager compresse la mémoire en arrière-plan, ce qui grignote l'utilisation du CPU. Vous devez nettoyer les applications ayant un taux d'utilisation élevé avant l'exécution pour empêcher l'activation du moteur de compression.
Épinglage des cœurs CPU et configuration du script d'exécution
Le décodage MoE sollicite continuellement le cache du CPU et le bus mémoire. Sur une station de travail à sockets multiples, si le nœud NUMA est mal sélectionné, la vitesse de génération des tokens est divisée par deux en raison des goulets d'étranglement du bus d'interconnexion.
Rédaction du script de lancement intégré
Vérifiez d'abord le numéro du nœud CPU physique directement relié au contrôleur NVMe externe à l'aide de numactl --hardware. Ensuite, rédigez un script shell qui évite l'attribution en double de l'hyper-threading et fixe les threads uniquement sur les cœurs physiques.
`bash
#!/usr/bin/env bash
set -euo pipefail
1. Configuration de la mémoire virtuelle du noyau
sudo sysctl -w vm.swappiness=1 > /dev/null
sudo sysctl -w vm.overcommit_memory=1 > /dev/null
sudo sysctl -w vm.max_map_count=524288 > /dev/null
sudo sysctl -w vm.vfs_cache_pressure=50 > /dev/null
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches > /dev/null
2. Suppression de la limite de verrouillage de la mémoire
ulimit -l unlimited
3. Liaison des threads OpenMP aux cœurs physiques
export OMP_NUM_THREADS=16
export OMP_PROC_BIND=TRUE
export OMP_PLACES=cores
4. Exécution avec liaison au nœud NUMA 0
ENGINE_BIN="./colibri_engine"
MODEL_PATH="/mnt/nvme_ext/GLM-5.2-colibri-int4-g64-with-int8-mtp"
exec numactl --physcpubind=0-15 --membind=0
"${ENGINE_BIN}"
--model "${MODEL_PATH}"
--threads "${OMP_NUM_THREADS}"
`
Sur un MacBook Apple Silicon, il faut empêcher le démon QoS de macOS de transférer les threads de travail vers les cœurs d'efficacité (E-Cores). Exécutez le processus en augmentant de force sa priorité :
`bash
sudo nice -n -20 taskpolicy -c default ./colibri_engine --model /Volumes/SSD/glm52_i4
`
Une fois ces quatre configurations terminées, vous pourrez lancer de manière stable un modèle MoE de 744B sur un MacBook ou une station de travail dotés de 32 Go de RAM via la méthode de diffusion en continu depuis le disque local, et ce sans plantage lié à un manque de mémoire (OOM).