2 points par GN⁺ 5 시간 전 | 1 commentaires | Partager sur WhatsApp
  • colibrì est un moteur en C pur conçu pour exécuter GLM-5.2 744B MoE sur une machine grand public avec environ 25 Go de RAM, en diffusant depuis le disque les experts routés, sans GPU
  • Son architecture clé consiste à garder en RAM la partie dense d’environ 17B paramètres en int4 pour 9,9 Go, tandis que les 21 504 experts routés sont stockés sur environ 370 Go de disque et lus à la demande
  • L’implémentation inclut le forward GLM-5.2, l’attention MLA et un KV-cache compressé, un routeur de style DeepSeek-V3, le speculative decoding MTP, des kernels de quantification int8/int4/int2, et un tokenizer BPE au niveau octet
  • Dans un environnement WSL2 avec 12 cœurs, 25 Go de RAM et un VHDX sur NVMe, le temps de chargement est d’environ 30 secondes, le pic de RSS en chat tourne autour de 20 Go, et le décodage à froid lit environ 11 Go sur disque par token, pour un débit de 0,05 à 0,1 tok/s
  • Sur un NVMe plus rapide et avec davantage de RAM, le cache et le pinning deviennent essentiels, et une mesure communautaire sur un Apple M5 Max avec 128 Go a relevé 1,06 tok/s avec MTP désactivé

Le problème que colibrì cherche à résoudre

  • colibrì se présente comme un « Tiny engine, immense model » et est un moteur capable d’exécuter le modèle MoE GLM-5.2 de 744B paramètres sur une machine grand public disposant d’environ 25 Go de RAM
  • Le runtime est en C pur, sans dépendance à Python, BLAS ou au GPU
  • Le moteur est constitué d’un unique fichier C c/glm.c d’environ 1 300 lignes, plus quelques petits headers
  • L’exécution d’exemple est ./coli chat, avec une sortie du type colibrì v1.0 — GLM-5.2 · 744B MoE · int4 · streaming CPU, un temps de préparation d’environ 32 secondes et 9,9 Go de mémoire résidente

Exécution d’un MoE basée sur le streaming disque

  • Le modèle Mixture-of-Experts 744B de GLM-5.2 n’active qu’environ 40B paramètres par token
  • Les experts routés, qui changent à chaque token, représentent environ 11 Go
  • La partie dense reste résidente en RAM
    • y compris l’attention, les experts partagés et les embeddings
    • soit environ 17B paramètres
    • avec une consommation d’environ 9,9 Go de RAM en int4
  • Les experts routés sont stockés sur disque
    • 75 couches MoE × 256 experts, plus la tête MTP
    • soit 21 504 experts routés au total
    • environ 19 Mo par expert en int4
    • pour un usage disque total d’environ 370 Go
  • Le chargement des experts utilise un cache LRU par couche, un hot-store épinglé optionnel et le page cache de l’OS

Principales fonctionnalités implémentées

  • Le forward GLM-5.2 est implémenté selon la structure glm_moe_dsa, avec une validation token-exact par rapport à l’oracle transformers
    • teacher-forcing 32/32 sur un modèle tiny-random
    • greedy generation 20/20
  • L’attention MLA inclut q/kv-LoRA et un RoPE partiel entrelacé
    • le KV-cache compressé utilise 576 floats par token
    • soit 57 fois moins que les 32 768 floats/token d’origine
    • GLM-5.2 a 64 heads et n’utilise pas GQA
  • Un routeur sigmoid de style DeepSeek-V3 est implémenté
    • noaux_tc
    • routed_scaling_factor
    • expert partagé
    • couches first-3-dense
  • Le speculative decoding MTP natif utilise la tête multi-token-prediction de GLM-5.2 (couche 78) pour produire des draft tokens, que le modèle principal vérifie via un forward batché
    • la tête MTP doit être en int8
    • en int4, le taux d’acceptation des drafts chute à 0~4 %, ce qui empêche la spéculation de fonctionner
    • en int8, l’acceptation mesurée est de 39~59 %, avec 2,2~2,8 tokens/forward
    • le tout reste lossless pendant l’échantillonnage grâce au rejection sampling
    • avec un cache froid, les drafts vérifiés peuvent router des experts supplémentaires et faire passer les expert-loads/token d’environ 660 à environ 1100
    • dans ce cas, cela peut prendre plus de temps tant que le cache et les pin ne sont pas warm-up, d’où la présence d’un garde-fou adaptatif et de l’option DRAFT=0
  • Le true sampling prend en charge temperature et nucleus
    • valeurs par défaut de 0,7 / 0,90
    • le réglage officiel 1,0 / 0,95 est décrit comme échantillonnant le bruit de quantification de la queue en int4
  • Le kernel integer-dot utilise des activations int8 de style Q8_0 et maddubs AVX2
    • le matmul int8 est 1,4 à 2,5 fois plus rapide
    • performance mesurée de 119 GFLOP/s
    • l’int4 est 1,8 fois plus rapide en batch
    • l’int4 sur une seule ligne est mesuré comme plus lent et reste donc en f32
  • L’absorption des poids MLA évite la reconstruction k/v par token pendant le decode
    • la requête absorbe kv_b, et le contexte est projeté après l’attention
    • même avec absorption forcée, la validation donne TF 32/32 et generation 20/20
  • L’expert readahead lit le bloc d’expert suivant avec WILLNEED pendant qu’un bloc d’expert est en cours de multiplication
  • Les kernels de quantification prennent en charge int8, int4 compacté, int2 compacté, les scales par ligne, AVX2 et le dequant-on-use
  • L’attention sparse DSA est en cours
    • les poids lightning-indexer prennent la forme d’une extraction d’environ 108 Go depuis le repo FP8
    • le forward de l’indexer est prévu pour l’étape suivante
    • d’ici là, l’attention reste dense et exacte pour un contexte ≤ 2048 tokens
  • En prefill et en vérification MTP, le Batch-union MoE lit les experts uniques du batch une seule fois, puis les applique à toutes les positions routées vers ces experts
  • Le tokenizer est un tokenizer BPE au niveau octet implémenté en C, utilisant une regex de propriétés Unicode de style GPT-2 et 320k merges
  • Un garde-fou RAM redimensionne automatiquement le cache d’experts au démarrage selon MemAvailable
    • en tenant compte de la projection de pic pour le working set, le KV, la ligne MTP et le buffer de reconstruction
    • afin d’éviter l’intervention de l’OOM-killer du noyau
  • Un convertisseur hors ligne est fourni sous la forme de c/convert_fp8_to_int4.py
    • il télécharge les shards FP8 de GLM-5.2 un par un
    • les déquantifie avec des block scales 128×128 avant de les requantifier dans le conteneur du moteur
    • comme les shards sont supprimés au fur et à mesure, l’intégralité du checkpoint FP8 de 756 Go n’a pas besoin d’être présente sur disque en même temps
    • la conversion peut être reprise

Performances de base mesurées et limites

  • L’environnement de développement est WSL2, 12 cœurs, 25 Go de RAM, NVMe via VHDX
  • Les mesures sont les suivantes
    • taille du modèle en conteneur int4 : environ 370 Go
    • RAM résidente : 9,9 Go
    • temps de chargement : environ 30 secondes
    • pic RSS en chat : environ 20 Go, limité automatiquement
    • coût du decode à froid : environ 11 Go de lectures disque par token
    • plafond disque aléatoire via VHDX : environ 1 Go/s
    • vitesse de decode à froid : environ 0,05~0,1 tok/s
    • spéculation MTP : 2,2~2,8 tok/forward avec tête int8
  • Il est précisé qu’il ne s’agit pas d’un système rapide, et que le cache chaud, les hot experts épinglés et MTP réduisent la latence utile de réponse
  • Deux points d’attention sont mentionnés pour les SSD
    • le streaming de colibrì est en lecture seule, donc la lecture elle-même n’a pas d’impact significatif sur l’usure du SSD
    • si un manque de RAM provoque du trafic de swap, cela entraîne des écritures qui peuvent user le disque
    • un duty cycle prolongé en lecture à plein régime peut faire chauffer les disques d’entrée de gamme, ce qui impose une surveillance de la température et de l’état de santé

Téléchargement et exécution du modèle

  • Le modèle GLM-5.2 int4 préconverti est disponible sur Hugging Face
  • En utilisant ce modèle préconverti, on peut sauter l’étape de conversion FP8 → int4
  • L’exécution consiste à pointer COLI_MODEL vers le répertoire du modèle
COLI_MODEL=/path/to/GLM-5.2-colibri-int4 ./coli chat
  • Le quick start exécute ./setup.sh dans le répertoire c pour vérifier gcc/OpenMP, compiler et lancer les self-tests
  • Pour convertir soi-même, il faut utiliser ./coli convert --model /nvme/glm52_i4
    • un chemin ext4/NVMe avec environ 400 Go d’espace libre est nécessaire
    • la conversion requiert Python ainsi que torch, safetensors, huggingface_hub, numpy
    • le moteur d’exécution lui-même reste en C pur et Python n’est utilisé que pour le convertisseur ponctuel
  • Les options utiles sont les suivantes
    • --temp T : sampling temperature, défaut 0,7 et nucleus 0,90, 0 pour greedy
    • --topp 0.7 : adaptive expert top-p, réduit l’usage disque de 30~40 %
    • --ngen N : nombre maximal de tokens par réponse
    • AUTOPIN=0 : désactive l’auto-pin du learning cache
    • THINK=1 : active le bloc de raisonnement de GLM-5.2
    • DRAFT=n : profondeur des drafts MTP
    • TF=1 : validation teacher-forcing

Learning cache et attentes selon le matériel

  • Le learning cache enregistre, à l’usage, les experts routés dans .coli_usage à côté du modèle
  • Au démarrage, il épingle automatiquement les experts les plus chauds dans la RAM disponible
  • La description du projet indique que plus l’usage s’accumule, plus colibrì devient rapide
  • L’environnement requis est Linux ou WSL2, gcc avec support OpenMP, AVX2, au moins 16 Go de RAM, et un NVMe local contenant le modèle int4 d’environ 370 Go
    • un chemin ext4 est recommandé
    • il est conseillé de ne pas utiliser de montage network/9p
  • La procédure de test consiste à compiler et lancer les self-tests, mesurer le disque avec iobench, vérifier tok/s, expert hit-rate et RSS en chat, enregistrer l’usage des experts puis appliquer le pinning, avant d’exécuter les benchmarks de qualité
  • Les attentes selon le matériel sont des estimations, non des mesures
    • machine de développement WSL2 VHDX, environ 1 Go/s, 25 Go de RAM : 0,05~0,1 tok/s à froid
    • Linux natif, NVMe PCIe4 aléatoire 3~5 Go/s, 32 Go : 0,5~1 tok/s
    • NVMe PCIe5 ou RAID0 de 2×NVMe à 8~12 Go/s, 64 Go, PIN d’environ 40 Go : 2~4 tok/s
    • 128~256 Go de RAM, 12 cœurs, hot experts en cache : 2~4 tok/s
    • même RAM avec 24~32 cœurs ou kernels AVX-512/VNNI : 5~15 tok/s

Benchmarks communautaires

  • Sur un Intel Core Ultra 7 270K Plus, 24 threads, WSL2, 24 Go de RAM et NVMe VHDX, la configuration par défaut atteint 0,07 tok/s
    • disk iobench : 1,96 Go/s buffered, 2,74 Go/s O_DIRECT
    • expert hit : 3~4 %
    • RSS : 14,1 Go
  • Dans ce même environnement, --topp 0.7 atteint 0,11 tok/s
    • expert hit : 11 %
    • RSS : 14,7 Go
    • le débit end-to-end augmente de 1,6 fois
  • Sur un Apple M5 Max, 18 cœurs, macOS, 128 Go de mémoire unifiée et SSD interne, la mesure atteint 1,06 tok/s
    • disk iobench : 14,2 Go/s O_DIRECT
    • configuration par défaut, MTP désactivé
    • expert hit : 23 %
    • RSS : 21,8 Go
  • Sur les machines avec 24 Go de RAM, le cache d’experts est automatiquement limité à 2 slots par couche, si bien que même avec un disque 2 à 2,7 fois plus rapide que la machine de développement, le decode reste en état froid
  • Sur les petites machines en RAM, le plafond RAM devient le goulot d’étranglement avant le disque
  • La mesure sur M5 Max montre qu’un modèle 744B peut atteindre environ 1 tok/s sur le SSD d’un laptop, et qu’avec un disque à 14 Go/s, le goulot d’étranglement se déplace vers le budget RAM et les kernels

Benchmarks de qualité et mesures restantes

  • Le coût de la quantification int4 sur la précision n’a pas encore été mesuré
  • Le benchmark harness est implémenté, mais sur le disque d’environ 1 Go/s de la machine de développement, une exécution complète prend une large partie de la journée
  • Les commandes de benchmark fournies exécutent hellaswag, arc_challenge, mmlu
cd c
./coli bench
./coli bench hellaswag --limit 200
./coli bench mmlu arc_challenge --ram 100
  • La sortie donne l’accuracy par tâche, avec un scoring en log-likelihood et un style EleutherAI-harness
  • Les scores publics de GLM-5.2 en full precision sont indiqués comme étant d’environ 85~95 % sur ces tâches
  • Si le conteneur int4 se situe à quelques points près, la quantification sera validée ; sinon, cela justifiera d’investir dans une quantification mixed ou grouped-scale

Structure du dépôt et licence

  • Les principaux fichiers sont les suivants
    • c/glm.c : moteur incluant le forward GLM-5.2, le streaming MoE, MTP et le mode serve
    • c/st.h : lecteur safetensors basé sur pread et fadvise, sans mmap
    • c/tok.h : tokenizer BPE byte-level en C
    • c/coli : CLI pour chat, run, bench, convert, info
    • c/iobench.c : microbenchmark disque parallèle tel que perçu par le moteur
    • c/convert_fp8_to_int4.py : convertisseur FP8 → int4 sûr pour le disque
    • c/make_glm_oracle.py : générateur d’oracle tiny-random pour la validation
    • c/olmoe.c : moteur Stage-A et première cible de validation
  • Le nom colibrì vient du colibri, capable de vol stationnaire malgré sa faible masse et de visiter de nombreuses fleurs dans une journée
  • La licence du projet est Apache 2.0, et les poids GLM-5.2 sont publiés sous licence MIT par Z.ai

1 commentaires

 
GN⁺ 5 시간 전
Commentaires Hacker News
  • Ce qui m’intéresse le plus, c’est de savoir si, en pratique, on est à un niveau mesurable en tokens/seconde, ou si on est plutôt proche de 1 token par minute
    Même quand un LLM local est aussi lent que 1 token/seconde, je l’ai quand même trouvé assez utile si je peux lui confier un projet pendant la nuit et vérifier le résultat 6 à 8 heures plus tard
    En revanche, les 0,05 à 0,1 token/seconde du matériel minimal mentionné dans le lien semblent avoir très peu de cas d’usage réellement exploitables
    Cela dit, le concept global est excellent, et j’espère voir plus de tentatives pour faire tourner localement, sur du matériel abordable pour le grand public, des modèles de la classe 350B à 900B, même si ce n’est qu’à 1 token/seconde
    J’aime bien l’idée de se dire : « on a un SSD NVMe avec de bonnes performances en lecture, et un énorme modèle sur disque local, donc au lieu de tout charger, lisons 11 Go/token à la demande »

    • C’est drôle de voir à quel point Claude Cowork m’a habitué à attendre les temps de réponse
      À ce rythme, je pense qu’au plus tard vers 2028 je ferai tourner ça en local
      Je vise un budget inférieur à 10 000 dollars, avec l’objectif d’exécuter un modèle comparable aux meilleurs d’aujourd’hui
    • Pour des modèles locaux lents ou certains projets, une interface de chat n’est peut-être pas la bonne approche, et quelque chose qui ressemble davantage à un système de tickets conviendrait peut-être mieux
      Avant de dépenser de l’argent dans un Mac Studio ou un GPU, j’ai trouvé un moyen de valider cette idée avec le matériel que j’ai déjà, et je pense pouvoir monter une preuve de concept cette semaine
      L’idée n’est pas nouvelle, j’ai juste besoin de temps pour l’adapter à ma façon de travailler
    • Il y a des benchmarks dans le README montrant Colibrì sur différents matériels, et j’y ai déjà vu des temps plutôt bons
      J’essaie encore de continuer à améliorer ça
    • Pour la plupart des projets, il est plus pratique d’utiliser un cloud qui propose GLM 5.2 gratuitement
      1 token par minute, c’est un débit trop faible même comparé aux limites d’usage gratuit
    • En ce moment, je me demande si le « grand public » peut vraiment se permettre 24 Go de RAM et un SSD NVMe de 0,5 To
  • Je suis en train de construire quelque chose de similaire pour macOS sur Apple Silicon
    L’idée est d’utiliser Unsloth split GGUF, une résidence partielle compressée en mémoire unifiée, des kernels Metal natifs, et un KV compressé natif uniquement en RAM
    Ça a sans doute plus de sens sur 128 Go que sur mes 64 Go, et je pense publier ça sur GitHub quand ce sera prêt

    • J’ai un MacBook Pro M4 128 Go qui attend, donc je serais ravi de tester
  • J’ai essayé une stratégie similaire du côté génération d’images/vidéos avec https://github.com/cretz/thinfer
    Il y a beaucoup de travail sur la branche video
    Comme j’avais sans cesse besoin d’un moteur d’inférence capable de charger et décharger les poids en mode LRU au moment opportun, j’ai fini par faire en vibe coding un outil qui accepte --vram-budget et reste dans cette limite
    Il s’est avéré que le coût de déplacer des octets mmapés vers et hors de la VRAM est assez faible comparé au calcul, et qu’en combinant pipeline et double buffering, on se retrouve presque toujours limité par le calcul plutôt que par la mémoire
    Bien sûr, j’utilise des modèles bien plus petits

    • On dirait que tu as intégré énormément de modèles, comme krea, wan, hunyan, etc.
      Je me demande si tu as créé un harnais commun capable de tous les exécuter
      Je me demande aussi quels modèles restent le plus régulièrement dans le budget VRAM
  • llama.cpp prend en charge diverses quantifications à 4 bits ou moins et mmap les modèles par défaut, donc il n’a pas besoin de garder l’intégralité des poids en mémoire
    Le système d’exploitation va les récupérer depuis le stockage quand c’est nécessaire
    C’est impressionnant d’avoir implémenté ça avec très peu de code et sans dépendances, mais je me demande s’il y a un réel gain de performance

  • C’est assez génial
    J’ai joué avec GLM 5.2 cette semaine et, de la même façon, j’ai trouvé ça impressionnant
    Dans mon entreprise, on teste une exécution locale sur du matériel extrêmement coûteux avant de lancer notre prochain projet, donc c’est sympa de voir des gens faire tourner cette énorme sortie open source sur des machines moyennes
    Même si ce n’est pas encore très pratique, c’est du bon travail

    • Je suis curieux de savoir sur quel matériel
  • Je ne connais pas très bien l’architecture interne des LLM, mais je me demande si une structure similaire pourrait s’appliquer à d’autres modèles qui ont des « couches »
    Une fois qu’une couche a fini son travail, pourrait-on la retirer de la RAM, charger la suivante depuis le disque, puis l’activer sur le résultat de la première ?

  • J’ai une machine avec 64 Go de RAM et 24 Go de VRAM, donc je me demande s’il est possible de régler ça pour utiliser davantage de RAM
    Ou peut-être faire tourner Gemma/Qwen sur le GPU et laisser GLM-5.2 lui déléguer les tâches plus petites
    Il faudrait peut-être aussi réentraîner GLM-5.2 dans une certaine mesure
    Je me demande aussi si l’utilisation de plusieurs disques en parallèle pour augmenter la bande passante pourrait améliorer la vitesse
    L’avertissement sur l’usure du SSD indique qu’au démarrage à froid, avec environ 11 Go/token de lectures aléatoires, la charge est lourde ; les lectures elles-mêmes sont sûres, mais le cache de pages du système d’exploitation peut provoquer des écritures, et un usage excessif peut accélérer l’usure des SSD bon marché
    Une approche sûre serait peut-être de créer une partition distincte pour les poids du modèle et de la monter en lecture seule
    Je ne sais pas si le cache de pages est géré par partition ou par disque, mais si c’est par disque, on pourrait peut-être formater un data.iso en lecture seule comme une partition et le monter comme disque

    • J’utilise un petit portable
      Si j’avais plus de disques, je pourrais effectivement tester ça
      Si tu obtiens des benchmarks, poste-les en pull request ou en issue et on pourra regarder ça ensemble
    • Je me demande si l’idée selon laquelle « le cache de pages du système d’exploitation peut provoquer des écritures » relève d’une hallucination
      Qu’est-ce que je rate ? Pourquoi de grosses lectures produiraient-elles des écritures ?
    • Ça pourrait être une bonne idée, et on pourrait travailler dessus ensemble
  • Il y a un avertissement sur l’usure du SSD sur la page https://github.com/JustVugg/colibri#ssd-wear-warning
    Avec un PC de bureau assemblé soi-même, on peut remplacer le SSD, mais que doivent faire les utilisateurs avec un SSD soudé ?
    Faut-il éviter ce genre d’application, ou accepter le risque d’une panne prématurée du stockage et continuer quand même ?
    J’imagine qu’il faudrait utiliser un stockage externe comme un SSD consommable

    • Oui, mieux vaut éviter
      Avec un portable équipé d’un SSD soudé, il faut clairement surveiller l’usage et être prudent
      Ce projet ressemble plus à une expérience qu’à quelque chose destiné à tout le monde, mais c’est quand même assez cool
    • Si j’ai bien compris, l’avertissement concerne le swap out sous forte utilisation mémoire
      Pas besoin d’aborder ça de manière superstitieuse
      L’activité disque, et en particulier les écritures, se mesure
      Par exemple, sous Linux, on peut utiliser iostat ou vmstat
    • C’est un avertissement assez conservateur
      L’application n’effectue pas d’écritures, donc elle n’use pas réellement le SSD en elle-même
      Le reste relève davantage de bonnes pratiques générales d’administration, indépendantes de l’application
    • Pour ce genre d’usage, un disque externe semble effectivement plus approprié
  • C’est techniquement impressionnant, mais je me demande si c’est vraiment exploitable en pratique

  • J’étais en fait en train de construire quelque chose de similaire moi aussi, mais je suis parti sur l’idée de mmap tout le modèle en mémoire afin d’éviter l’usage de RAM supplémentaire
    J’ai aussi fait implémenter à Claude Medusahttps://arxiv.org/abs/2401.10774 sur le modèle, pour tenter d’obtenir les bénéfices de la prédiction multi-token sans charger de modèle additionnel en mémoire
    Je viens de rentrer chez moi, donc pour ajouter un détail : je fais toutes les modifications dans llama.cpp, et l’objectif final est de déployer ça sur quelque chose comme un ordinateur monocarte qui gère des fichiers mmapés sur NVMe
    D’après mes tests précédents, la limite théorique de la configuration actuelle semble être d’environ 1,8 token/seconde, mais les têtes Medusa supplémentaires ne sont pas encore totalement entraînées, et pour être honnête je ne sais même pas très bien si elles sont comptées dans les tokens générés
    Au final, l’idée semble similaire, sauf que comme je ne sais pas encore écrire un parseur/runner LLM from scratch, au lieu de décider moi-même ce qu’il faut garder en mémoire, j’ai laissé le noyau Linux s’en charger
    Enfin, j’ai limité l’usage de llama.cpp à 16 Go sur mes 32 Go, donc il est peut-être possible de descendre encore plus bas

    • Colibrì a encore besoin d’améliorations, donc si tu as des idées ou autre, les pull requests, issues et benchmarks sont tous les bienvenus
    • C’était précisément le type d’approche qui m’intriguait