1 points par GN⁺ 4 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Sur un serveur équipé de deux Xeon E5-2690 v2 de 2013 et de DDR3, Gemma 4 26B-A4B Q8_0 a pu être exécuté uniquement sur CPU, avec environ 5,2 tokens/s en décodage et 16 tokens/s en évaluation de prompt
  • Le chemin rapide de ik_llama.cpp suppose AVX2·FMA3, mais les CPU Ivy Bridge ne prennent en charge qu’AVX1 ; il a donc fallu modifier la compilation pour un build sans AVX2 et ajouter des fallbacks d’exécution
  • Le constructeur de graphe générait systématiquement MOE_FUSED_UP_GATE et FUSED_UP_GATE, alors que le dispatcher non AVX2 n’avait aucun chemin pour les traiter, ce qui laissait environ 240 tenseurs non calculés à chaque passe avant et produisait une sortie multilingue incohérente
  • Claude a identifié l’erreur via l’instrumentation des logits et l’analyse du code, puis a remplacé les opérations fusionnées par deux ggml_mul_mat_id et ggml_fused_mul_unary, tandis que l’utilisateur exécutait les expériences et déterminait le référentiel d’un résultat correct
  • Même un ancien serveur d’entreprise peut servir de solution locale de secours en cas de panne d’API payante ou pour des traitements batch lents, mais il ne faut pas utiliser --run-time-repack, qui produit une disposition spécifique à AVX2

Environnement d’exécution sur un serveur de stockage vieux de 13 ans

  • L’équipement HP StoreVirtual recyclé utilise deux Xeon E5-2690 v2 de 2013 et de la mémoire DDR3, sans GPU
    • Il appartient à la génération Ivy Bridge, donc ne prend en charge qu’AVX1 et pas AVX2 ni FMA3
    • Il a été conçu à l’origine pour le stockage sur disque et a coûté moins de 300 dollars
  • Le modèle exécuté est Gemma 4 26B-A4B de Google, un modèle open weight Mixture of Experts (MoE) de 26 milliards de paramètres
  • Sur le modèle quantifié Q8_0, les mesures donnent environ 5,2 tokens/s en décodage et 16 tokens/s en évaluation de prompt

Le point de départ : un cas sur Xeon de 2016

  • L’article partagé sur Hacker News, A 10 year old Xeon is all you need, montre l’exécution de Gemma 4 sans GPU sur un Xeon unique de 2016 avec 128 Go de DDR3
  • Cette configuration utilise ik_llama.cpp et une vingtaine de drapeaux d’exécution très précis
    • décodage spéculatif
    • routage MoE adapté au CPU
    • flash attention pour CPU
    • repacking des poids à l’exécution
  • La même approche a été appliquée au serveur Ivy Bridge, mais l’exécution s’arrêtait dès le démarrage
    • Contrairement au CPU Broadwell de 2016, le E5-2690 v2 ne dispose ni d’AVX2 ni de FMA3
    • Cet ensemble d’instructions est disponible à partir de Haswell en 2014, soit la génération v3 d’Intel, et les kernels rapides supposaient sa présence

Diagnostic et patch avec Claude

  • Après avoir reçu les informations sur l’échec au démarrage, Claude a identifié comme cause la différence de jeu d’instructions CPU
  • Reprenant une tentative inaboutie effectuée auparavant avec un modèle gratuit, il a modifié les chemins C++ critiques pour que les fallbacks fonctionnent correctement sur les CPU antérieurs à AVX2
  • Le travail ne s’est pas résumé à une simple demande du type fix it
    • lecture de code C++ écrit par d’autres développeurs avec un fort accent sur la performance
    • analyse de la raison pour laquelle certains kernels n’étaient pas valides sur une microarchitecture donnée
    • contournement des chemins non pris en charge sans supprimer les optimisations déjà présentes dans le fork
  • Au lieu d’écrire lui-même les fallbacks des kernels C++, l’utilisateur a exécuté les expériences, lu les sorties, formulé les questions suivantes et déterminé le référentiel d’un résultat correct
  • Le diagnostic et le patch ont été réalisés par une instance de Claude exécutée sur ce même serveur

Chemins d’opérations cassés dans un build sans AVX2

  • Le ik_llama.cpp utilisé pour l’inférence MoE de Gemma 4 est un fork de llama.cpp qui suppose par défaut AVX2 comme condition minimale
  • Lors de la compilation, désactiver GGML_USE_IQK_MULMAT retire la plupart des chemins rapides au profit de fallbacks scalaires/SSE plus génériques
    • Ce fallback fonctionne pour la multiplication de matrices Q8_0 classique
    • En revanche, il ne s’appliquait pas à deux opérations du graphe
  • Le réseau feed-forward MoE de Gemma 4 génère les opérations suivantes
    • MOE_FUSED_UP_GATE : opération fusionnée combinant, par expert, les multiplications de matrices gate et up avec SwiGLU
    • FUSED_UP_GATE : version dense utilisée dans les couches non MoE
  • Dans le dispatcher de calcul, ces deux opérations étaient protégées par la condition GGML_USE_IQK_MULMAT, mais le constructeur de graphe les générait sans condition
    • Le dispatcher du build sans AVX2 ne contenait aucun case pour traiter ces enums
    • Les opérations tombaient dans la branche par défaut, et les tenseurs de destination de tous les réseaux feed-forward experts restaient silencieusement non calculés
  • Comme Gemma 4 26B active 8 experts par token sur 30 couches, cela conduisait à consommer environ 240 tenseurs par passe avant, remplis de valeurs résiduelles présentes dans les buffers mémoire

Les indices visibles dans une sortie incohérente

  • La sortie erronée semblait fluide, mais n’était qu’une suite multilingue dénuée de sens
    • les IDs de tokens étaient répartis uniformément sur l’ensemble du vocabulaire de 262 000 entrées
    • thaï, coréen, sentinelles <unused> et fragments anglais apparaissaient avec une fréquence comparable
  • Avec une température à 0, la sortie était déterministe ; les exécutions mono-thread et multi-thread produisaient exactement les mêmes octets, et aucun NaN n’apparaissait
  • Couche après couche, de grandes constantes poussaient les états cachés jusqu’à aplatir le softmax final
  • Claude a instrumenté les logits bruts avant l’échantillonnage pour afficher les 5 meilleurs tokens, la plage, la moyenne et le nombre de NaN
    • pour le premier token prédit, le logit moyen n’était pas proche de 0 mais de +16
    • environ 80 % du vocabulaire présentait un logit positif
  • Comme le biais restait constant, contrairement à une corruption aléatoire, l’analyse s’est resserrée sur l’hypothèse que de larges portions des états cachés utilisaient de la mémoire non initialisée, contenant de petites valeurs flottantes positives résiduelles

Une correction en trois étapes

  • Le patch se compose de trois commits appliqués au-dessus de main du fork
  • Correctifs de compilation sans AVX2

    • Les branches scalaires #else de quantize_row_q8_0_x4 et quantize_row_q8_1_x4_T dans iqk_quantize.cpp faisaient en réalité référence à des helpers AVX2 comme hsum_i32_8
    • Ces branches ont été réécrites sous forme de boucles scalaires portables
    • Des gardes #if GGML_USE_IQK_MULMAT ont été ajoutés à certains appels IQK qui avaient fuité dans ggml.c et ggml-quants.c
    • Les includes manquants ont été ajoutés pour que iqk_cpu_ops.cpp compile de manière autonome
    • Sans ces changements, le fork ne se compile pas du tout sur du matériel sans AVX2
  • Fallback de graphe à l’exécution

    • Plutôt que de corriger le dispatcher, le constructeur de graphe a été modifié pour générer, dans ce type de build, des opérations dont le chemin de calcul existe déjà
    • Dans ggml_moe_up_gate, lorsque GGML_USE_IQK_MULMAT est désactivé, le code traite les poids fusionnés up_gate_exps
    • La forme du tenseur est [n_embd, 2*n_ff, n_experts], avec la première moitié pour gate et la seconde pour up
    • Il est découpé en deux tranches via ggml_view_3d
    • ggml_mul_mat_id est exécuté sur chaque tranche
    • ggml_fused_mul_unary(gate, up, SILU) combine ensuite les deux résultats
    • Si les poids gate et up sont déjà séparés, le code utilise sans découpage deux appels à ggml_mul_mat_id suivis de l’opération fusionnée multiplication + unaire
    • Le même fallback a été appliqué à ggml_fused_up_gate, utilisé dans les couches non MoE
    • mul_mat_id repose sur l’implémentation ggml standard et fused_mul_unary exécute SILU et la multiplication en une seule opération ; il existait donc déjà une implémentation non IQK pour tous les composants nécessaires
    • L’ensemble des changements se trouve derrière #if !GGML_USE_IQK_MULMAT, si bien que les builds AVX2 restent strictement identiques bit à bit
  • Remise en état des stubs CI

    • Les stubs #else des sources IQK ne correspondaient pas à iqk_mul_mat.h, de sorte que ci/run.sh ne compilait pas non plus sur du matériel sans AVX2
    • L’include <cstdint> manquait
    • Certains stubs avaient un paramètre initial superflu ou omettaient le paramètre sinks, ce qui modifiait leur signature
    • D’autres fonctions n’avaient pas de stub du tout, provoquant des références non définies à l’édition de liens
    • Une fois les stubs réalignés sur le header, il est devenu possible d’exécuter la suite de tests en environnement non AVX2

Coût en performances du fallback et bug lié au repacking

  • Le fallback exécute deux mul_mat_id au lieu d’un seul kernel fusionné, ce qui ajoute un surcoût
  • Ce CPU est déjà limité par la bande passante mémoire, et le kernel fusionné d’origine étant de toute façon réservé à AVX2, aucun chemin d’exécution existant n’est perdu dans cet environnement
  • Les performances finales atteignent environ 5,2 tokens/s en décodage et 16 tokens/s en évaluation de prompt sur le MoE 26B-A4B
  • --run-time-repack réorganise au démarrage les poids quantifiés dans un format entrelacé Q8_0_R8 spécifique à AVX2
    • Il s’agit d’un bug distinct qui recorrompt la sortie en environnement AVX1
    • Le patch actuel ne le corrige pas et retire donc ce flag du script d’exécution

Comment la cause a été isolée

  • L’incompatibilité de jeu d’instructions était facile à constater, mais il était plus difficile d’identifier que le dispatcher tombait silencieusement dans la branche par défaut sans signaler d’erreur
  • La revue du code a permis d’éliminer plusieurs pistes
    • les helpers RMSNorm semblaient corrects
    • le fallback AVX1 de ggml_vec_dot_q8_0_q8_0 semblait lui aussi correct
    • les résultats identiques bit à bit en mono-thread ont permis d’exclure un bug de threading
  • Le fait que la moyenne des logits reste figée à +16 et que des tokens de longue traîne aient des valeurs proches a conduit à conclure qu’une grande partie du flux résiduel n’était pas initialisée
  • Après avoir recherché #if GGML_USE_IQK_MULMAT dans le dispatcher, les deux chemins d’opérations manquants ont été trouvés en environ une minute

Conditions de reproduction et cas d’usage

  • Les conditions de reproduction sur du matériel antérieur à AVX2 sont les suivantes
    • Matériel : deux Xeon E5-2690 v2, Ivy Bridge, AVX1, DDR3, sans GPU
    • Build : compiler ik_llama.cpp de la branche patchée sans GGML_USE_IQK_MULMAT
    • Modèle : Gemma 4 26B-A4B Q8_0
    • Exécution : utiliser les flags CPU habituels de ik_llama.cpp, à l’exception de --run-time-repack
  • Les changements exacts sont disponibles dans ikawrakow/ik_llama.cpp PR #2138
    • Au moment de la rédaction, la PR est encore ouverte et en attente de revue par un mainteneur ; il faut donc exécuter directement la branche
    • Les bugs observés sur le même type de matériel peuvent être signalés dans le fil de la PR
  • Conserver un modèle local sur un ancien serveur d’entreprise peut fournir une solution de secours lorsque les API payantes tombent en panne, ou servir à traiter des tâches batch lentes pour lesquelles une facturation au token n’est pas adaptée
  • L’auteur valorise davantage la capacité à creuser un code inconnu et des systèmes anciens que l’usage de services par abonnement ; la même approche s’applique aussi bien à la maintenance d’une application Rails vieille de 15 ans qu’à celle d’une base de données dont le responsable est parti

1 commentaires

 
GN⁺ 4 시간 전
Avis de Hacker News
  • On s’attend à ce que, d’ici la mi-2027, il soit possible de faire tourner des modèles MoE (mélange d’experts) de plus de 200 milliards de paramètres sur du matériel grand public
    Je fais tourner Qwen3.6-35B-A3B en local sur un Mac 16 Go et j’obtiens 7 à 9 tokens par seconde : https://github.com/deepanwadhwa/samosa-chat
    En gros, un modèle de niveau GPT-4 tourne en local à cette vitesse sur un MacBook Air avec 16 Go de RAM

    • Il ne sera peut-être même pas nécessaire d’attendre jusque-là. Bonsai 27B, le modèle à poids ternaires publié par Prism il y a quelques jours, ne fait qu’environ 7 Go et dépasse 44 tokens par seconde sur un portable M4 Max : https://huggingface.co/prism-ml/Ternary-Bonsai-27B-mlx-2bit
      On est déjà dans le même ordre de grandeur que le nombre de paramètres actifs de la plupart des modèles de plus de 200 milliards de paramètres, donc Prism pourrait sortir un tel modèle s’il le voulait. Cela dit, les réseaux neuronaux récurrents comme HRM n’ont pas besoin d’autant de paramètres, donc la nécessité réelle d’une telle échelle reste discutable : https://huggingface.co/sapientinc/HRM-Text-1B
    • 7 à 9 tokens par seconde, ce n’est pas vraiment une bonne vitesse. Je ne suis pas expert, mais en expérimentant en local, tout ce qui est plus lent que 12 à 16 tokens par seconde m’a paru pénible
    • Je suis globalement d’accord avec la prédiction, mais je pense que le calendrier pourrait être un peu plus tardif. Pour obtenir ce genre de résultats, il faudra une compression totalement différente de la simple réduction actuelle du nombre de bits par paramètre ; dans ce scénario, la mesure classique du nombre de paramètres pourrait elle-même ne plus avoir beaucoup de sens
      Par exemple, si chaque couche ne contenait qu’une graine de 256 bits passée dans une fonction de bruit pour générer 16K poids réels, on pourrait descendre sous 1 bit de stockage par poids
    • Je fais tourner Ornith 35B sur un Pi et j’obtiens plus de 50 tokens par seconde. Depuis que j’ai découvert que le Pi prend aussi en charge la recherche et la récupération, je n’ai plus besoin de chercher un gros modèle pour la recherche
      J’ai hâte de voir les nouveaux modèles qui devraient changer la donne ce mois-ci et le mois prochain. Ornith vaut vraiment le détour
    • À 9 tokens par seconde, ça me paraît difficilement supportable. Il m’arrive souvent d’utiliser un million de tokens en moins d’une journée dans une session Claude ; pour du vrai travail, ça semble beaucoup trop lent
  • Certains n’aimeront pas l’entendre, mais je pense que le coût des tokens chez les fournisseurs d’inférence est inférieur au coût de l’électricité pour une exécution locale
    En simplifiant et en ne comptant que la génération en sortie, 5 tokens par seconde font 18 000 tokens par heure, ce qui coûterait environ 0,005 dollar chez un fournisseur. En supposant que le serveur consomme environ 500 W pendant l’inférence, et avec un tarif électrique allemand de 0,3 dollar/kWh, produire la même quantité en local coûte 0,15 dollar, soit 30 fois plus cher
    Si la confidentialité est une préoccupation, l’exécution locale reste intéressante, mais il faut savoir qu’elle est bien moins efficace que les fournisseurs d’inférence. Avec les nouveaux GPU qui améliorent l’efficacité de l’inférence, cet écart va fortement se creuser
    Au départ, j’avais calculé à tort 180 000 tokens, mais il s’agit en réalité de 18 000 tokens ; à moins d’avoir une électricité quasiment gratuite, il est difficile de rivaliser. Les fournisseurs utilisent sans doute encore des H200/H100 pour les petits modèles, mais lorsqu’ils passeront aux GB300 ou aux GPU Ruby de l’an prochain, le coût d’inférence sera divisé par 30 et la principale valeur des modèles locaux sera probablement la protection de la vie privée

    • Chez moi, faire tourner Qwen 27B consomme environ 400 W en charge, génère autour de 40 tokens par seconde, et, plus important, traite les prompts à environ 1 000 tokens par seconde
      En une heure, cela permet de traiter 3,6 millions de tokens en entrée ou de générer 144 000 tokens, pour environ 0,15 dollar d’électricité. Avec Sonnet, la même quantité coûterait 7,2 dollars en entrée et 1,4 dollar en génération ; le cloud est donc 10 fois plus cher pour la génération et presque 50 fois plus cher pour le traitement
    • Aux États-Unis, on est loin de 0,30 dollar/kWh. Aux heures creuses, c’est la moitié, et on peut aussi investir 1 000 dollars dans une batterie pour stocker de l’électricité à 0,11 dollar pendant les périodes ultra bon marché
      Les fournisseurs d’inférence sont lourdement endettés et se battent pour des parts de marché, donc les prix augmenteront forcément
    • Ce serait génial de construire un serveur LLM auto-hébergé autonome, alimenté par solaire et batterie. Comme Low Tech Magazine, il serait hors ligne quand il n’y a pas assez de soleil, et dimensionné pour les besoins quotidiens d’une communauté locale : groupe d’amis, rue, association, etc.
      Si l’IA est suffisamment démocratisée pour être contrôlée directement par les communautés, cela résout aussi le problème des data centers, et le niveau de censure et d’alignement peut être décidé démocratiquement par la communauté. C’est une idée proche de certains textes de Geohotz
      Les modèles open source deviendront bientôt assez bons et efficaces pour être servis à bas coût sur du matériel d’occasion. Dans chaque petite communauté, il suffira qu’une personne à l’aise avec la technique réunisse quelques centaines de dollars de coût initial ; ensuite, le service pourra fonctionner presque gratuitement, sans facture d’électricité
      https://solar.lowtechmagazine.com/
    • L’efficacité compte, mais beaucoup de gens ont aussi une électricité bon marché ou du matériel efficace. Sur un serveur domestique AMD Strix Halo, on peut servir Gemma4-26B à environ 70 tokens par seconde en n’utilisant que 100 W
    • Peu m’importe que cela coûte plus cher. Je fais tourner des modèles en local non pas pour économiser de l’argent, mais pour obtenir liberté et confidentialité ; et l’existence d’une alternative finit aussi par faire baisser les coûts à long terme
      Sans l’option locale, les prix des modèles cloud seraient bien plus élevés
  • Les Xeon doubles de cette génération risquent de consommer plus de 300 W en charge. Au tarif moyen de l’électricité aux États-Unis, cela revient à 1,35 dollar par jour, et davantage s’il faut climatiser la pièce en été.
    Même en ignorant le temps de traitement du prompt et en le faisant tourner 24 h/24, on obtient environ 400 000 tokens par jour, soit environ 0,30 dollar par million de tokens en sortie. Par coïncidence, c’est le prix actuel de ce modèle sur OpenRouter, mais la vitesse de génération d’OpenRouter est 8 fois plus rapide.
    Il y a beaucoup de raisons d’expérimenter avec un LLM local, comme éviter que les données quittent la maison, mais il est difficile d’y gagner financièrement. Ayant moi-même investi bien plus d’argent dans du matériel d’inférence local à la maison, je trouve ça amusant, mais pas vraiment un moyen de faire des économies.

    • Pour quelqu’un qui habite dans une vraie maison, c’est un calcul raisonnable. Je vis en location et je ne paie pas directement l’électricité, donc la limite de rentabilité, c’est le moment où le propriétaire commence à se plaindre.
    • À mon avis, la plupart des gens ne font pas tourner les modèles en local pour économiser de l’argent, mais pour éviter de disséminer leurs données personnelles.
    • C’est un peu comme les cryptomonnaies. À cause du prix de l’électricité, pour la grande majorité des gens, acheter revient moins cher que miner soi-même.
    • Pendant les saisons froides où l’on chauffe la maison, on peut aussi utiliser la chaleur perdue, ce qui améliore encore la rentabilité.
  • J’ai résumé les résultats obtenus en faisant tourner plusieurs modèles sans GPU sur un double Xeon avec 256 Go de DDR4.
    https://gist.github.com/hparadiz/f3596d00a62d8ebb2dadcc46ee5822c7

    • Je me demande si tu as essayé de n’utiliser qu’un seul CPU pour éliminer la baisse de performances liée au NUMA. Cela limiterait l’usage à la moitié de la mémoire, mais j’aimerais savoir quelle différence de performances cela donne.
    • Avec une très grande quantité de RAM lente, même les petits modèles semblent tourner assez vite. Je me demande comment des modèles plus gros fonctionneraient sur cette machine.
  • C’est plutôt lent. Même sur un CPU de 13 ans, j’obtiens 8 à 12 tokens par seconde, selon la taille du contexte et d’autres réglages.
    https://news.ycombinator.com/item?id=48354801

    • Même avec un Xeon E3-1270 V2 à 3,50 GHz et une vieille Nvidia Quadro K2200 de 4 Go, j’obtiens 8 à 9 tokens par seconde. Je fais tourner gemma4:e2b et gemma4:12b-it-qat dans Ollama.
    • La différence ne vient-elle pas du fait que l’article original utilise une quantification Q8, alors qu’ici c’est du Q4 ?
  • Je suis l’auteur du billet original. Le commentaire d’origine semble avoir été signalé pour une raison quelconque. J’ai ouvert les modifications sous la forme de la PR #2138 du projet amont : https://github.com/ikawrakow/ik_llama.cpp/pull/2138

  • Sur cette machine StoreVirtual, il ne semble pas y avoir de port vraiment utilisable pour installer des logiciels. Il n’y a guère que des ports USB ; je me demande si l’installation se fait via une console série.

  • Avec la même configuration, je fais tourner Gemma 4 26B sur un Mac Pro de 2013 et j’obtiens environ 5 tokens par seconde. Les deux cartes graphiques ne servent à rien ici, mais pour certaines tâches c’est suffisamment pratique.

  • J’ai vu de mes propres yeux la configuration dans le sous-sol, et elle est vraiment impressionnante. La prochaine fois, j’aimerais bien voir aussi l’imprimante 3D.

  • Une vidéo assez liée : quelqu’un y fait tourner un LLM sur un Pentium 4 et l’a surnommé NetburstGPT. Évidemment, c’est très lent.
    https://www.youtube.com/watch?v=ILV-eu90te8