1 points par GN⁺ 2024-10-12 | 1 commentaires | Partager sur WhatsApp
  • lm.rs est un projet qui exécute localement l’inférence de modèles de langage sur CPU en Rust, avec pour objectif une implémentation minimale réalisant l’inférence complète sans bibliothèque de ML
  • Inspiré de llama2.c et llm.c de Karpathy, il ne prenait au départ en charge que Google Gemma 2, avant d’être étendu à Llama 3.2 et à la prise en charge d’images avec PHI-3.5
  • Une évolution récente a ajouté le traitement par lots, ce qui accélère l’encodage d’images jusqu’à environ 3x, et Llama 3.2 1B tourne à 50 tok/s sur la machine 16 cœurs de l’auteur
  • Les modèles préparés sont disponibles sur Hugging Face, et le README recommande d’utiliser Q8_0, en précisant que la quantification Q4_0 est encore en cours d’amélioration
  • Les utilisateurs peuvent télécharger directement un modèle et un tokenizer au format LMRS pour compiler immédiatement, ou convertir les fichiers de modèle d’origine de Hugging Face avec export.py et tokenizer.py

Objectifs de lm.rs

  • lm.rs est une implémentation d’inférence de modèles de langage locale sur CPU écrite en Rust
  • L’objectif est une implémentation minimale réalisant l’inférence complète d’un modèle de langage sur CPU, sans bibliothèque de ML
  • Le projet est inspiré de llama2.c et de llm.c
  • Le README indique que le code actuel n’est « pas si minimal », et que certaines parties peuvent encore être optimisées et améliorées
  • Le projet a aussi servi de point de départ à l’auteur pour découvrir Rust

Modèles pris en charge et extension multimodale

  • Au départ, seuls les modèles Google Gemma 2 étaient pris en charge, puis la prise en charge de Llama 3.2 a été ajoutée
  • Plus récemment, une option d’utilisation d’images a été ajoutée via PHI-3.5
  • Éléments actuellement mis en avant
    • prise en charge multimodale via le modèle PHI-3.5-vision
    • prise en charge du modèle texte seul PHI-3.5-mini
  • Ressources associées

Performances et modèles préparés

Flux de conversion des modèles

  • En téléchargeant des modèles quantifiés préparés et des tokenizers depuis Hugging Face, il est possible d’ignorer l’étape de conversion
  • Pour convertir directement des modèles publiés sur Hugging Face par Google ou Meta, il faut installer des dépendances Python supplémentaires
pip install -r requirements.txt
  • Les fichiers .safetensors et config.json sont téléchargés depuis la page du modèle d’origine
  • Pour les modèles multimodaux comme PHI3.5 Vision, un fichier config de CLIP est également nécessaire
  • export.py convertit les poids en bfloat16 au format LMRS
python export.py --files [ordered .safetensor files] --config [model config.json] --save-path [name and path to save] --type [model type (GEMMA/LLAMA/PHI)]
  • Pour exporter une version quantifiée, il faut utiliser les options --quantize et --quantize-type
  • La taille d’un modèle quantifié en int8 peut être réduite d’environ 9.8G à environ 2.5G selon la taille de groupe
  • Les modèles multimodaux doivent inclure l’argument --vision-config
  • tokenizer.py convertit le modèle de tokenizer au format tokenizer LMRS
python tokenizer.py --model-id [huggingface model_id] --tokenizer-type [type of the tokenizer (GEMMA/LLAMA/PHI)]

Compilation et exécution

  • Le code Rust se compile avec cargo, et le README précise de passer l’option target-cpu
RUSTFLAGS="-C target-cpu=native" cargo build --release --bin chat
  • Pour activer les fonctions multimodales, il faut ajouter l’argument --features multimodal
  • L’exécution de base se fait en indiquant le fichier de poids du modèle
./target/release/chat --model [model weights file]
  • Des arguments supplémentaires permettent d’utiliser le tokenizer, temperature, top-p, show-metrics, etc.
  • Les arguments disponibles sont consultables avec --help
  • Avec un modèle multimodal, le chemin de l’image se spécifie via l’argument --image
  • Pour PHI3.5-vision, le README recommande une temperature à 0

Exécuter le backend WebUI

  • Pour lancer le backend destiné à WebUI, il faut compiler avec la fonctionnalité backend
RUSTFLAGS="-C target-cpu=native" cargo build --release --features backend --bin backend
  • Le backend multimodal active la fonctionnalité backend-multimodal
  • Le backend se lance en indiquant le fichier de poids du modèle
./target/release/backend --model [model weights file]
  • --ip et --port permettent de modifier l’IP et le port
  • Des options supplémentaires comme temperature peuvent aussi être utilisées
  • La compatibilité multimodale s’active avec l’option --multimodal
  • Une fois lancé, il est possible de se connecter via l’interface web

État des TODO et licence

  • Éléments terminés
    • ajout d’autres méthodes d’échantillonnage
    • dans les tests des modèles 9B et 27B, le test 9B est terminé, le 27B est indiqué comme trop lent
    • parallélisation de la boucle d’attention multi-têtes
    • ajout d’indicateurs de performance
    • prise en charge de la quantification int8 et int4
  • Éléments restants
    • fonction de fourniture d’un prompt système
  • La licence est MIT

1 commentaires

 
GN⁺ 2024-10-12
Avis sur Hacker News
  • Sur un MacBook M2 64 Go, j’ai essayé de lancer le fichier de 1,2 Go llama3.2-1b-it-q80.lmrs et cela m’a paru assez rapide ; d’après Activity Monitor, il utilisait 1000 % de CPU sur 13 threads.
    J’ai cloné lm.rs dans /tmp, compilé avec RUSTFLAGS="-C target-cpu=native" cargo build --release --bin chat, puis téléchargé tokenizer.bin et llama3.2-1b-it-q80.lmrs depuis Hugging Face, avant de lancer ./target/release/chat --model llama3.2-1b-it-q80.lmrs.

    • Quelqu’un demande s’il serait possible de l’exécuter avec ./target/release/chat --model llama3.2-1b-it-q80.lmrs --show-metrics pour voir combien de tokens par seconde on obtient.
    • Après avoir entré une invite de conversation simple en français, il a d’abord semblé répondre, puis s’est rapidement effondré en une sortie interminable de charabia incompréhensible.
      Seule une partie a été conservée à cause du formatage, mais cela prenait la forme d’une longue suite continue de mots aléatoires.
    • Je me demande comment comprendre à quel point c’est intelligent par rapport au ChatGPT actuel.
  • L’article est très bien écrit, et je pourrais utiliser une partie du code source en cours pour expliquer comment les transformers fonctionnent réellement.
    Le code est plus concret et plus détaillé que les schémas de têtes d’attention. Cela dit, si la bibliothèque écrit directement sur stdout, elle risque de perturber la sortie d’applications, par exemple un éditeur de texte qui fournit une vérification de style ; il vaudrait mieux écrire dans un tampon de chaînes d’une instance de journalisation associée à l’objet lm.rs.
    Je vois aussi, dans le lecteur de modèle, un passage qui utilise unsafe pour imposer l’alignement des données ; je me demande s’il serait possible de faire cela sans unsafe, sans trop forcer.

    • Il vaut mieux appeler un callback utilisateur qu’écrire dans un tampon de chaînes.
      Cela permettrait par exemple d’afficher les logs dans une GUI.
  • J’ai développé pas mal d’outils Rust pour le chargement de modèles et diverses tâches LLM.
    Ils permettent notamment de choisir automatiquement le plus grand modèle quantifié selon la mémoire disponible, d’extraire le tokenizer depuis un gguf, ou encore d’insérer un prompt. Cela pourrait aider à supprimer certaines dépendances Python.
    Pour l’instant, c’est destiné à la prise en charge de llama.cpp, mais ceci est aussi assez intéressant. Je me demande aussi s’il est prévu de prendre en charge les contraintes grammaticales (grammar).
    https://github.com/ShelbyJenkins/llm_client

  • L’expression no dependency dans le titre manque de clarté.
    À première vue, j’ai pensé qu’il pouvait s’agir de no_std, mais en réalité ce n’est pas no_std et il semble bien y avoir quelques dépendances. Peut-être que cela signifie simplement qu’elles sont toutes en Rust.

    • Trouver un bon titre est difficile. Ce que je voulais dire, c’est qu’il n’y a pas de dépendances de deep learning comme PyTorch, CUDA ou ONNX, et que toute la logique est autonome.
      En toute transparence, il y a 5 dépendances Rust de base, dont chrono et clap, qu’il faudrait effectivement mettre derrière des feature flags pour la fonction de chat. Les 3 autres sont des crates utilitaires destinées à tirer un peu plus de performance du matériel : rayon pour faciliter la parallélisation, wide pour aider avec le SIMD, et memmap2 pour le memory mapping des fichiers de modèle.
    • En lisant le README, on a l’impression que requirements.txt nécessite PyTorch et plusieurs dépendances Python ; comme c’est aussi le seul endroit de la page où le mot « dependency » apparaît, la formulation du titre est assez déroutante.
      Le projet lui-même semble simplement utiliser le sous-titre « Minimal LLM inference in Rust ». D’après l’historique Git, le compte qui a posté cet article est contributeur mais ne semble pas être l’auteur principal ; une explication de ce que signifie exactement zero dependencies serait utile.
    • À l’origine, un titre comme « sans dépendance matérielle » ou « sans dépendance GPU » aurait peut-être eu du sens.
      Malheureusement, HN supprime parfois des mots des titres sans raison ni transparence particulières.
    • J’ai l’impression que le cargo de Rust est maintenant presque devenu comme npm.
      Je ne vois pas comment on peut parler de zéro dépendance quand il y en a 16.
  • J’avais créé quelque chose de similaire il y a quelque temps, mais les performances étaient décevantes par rapport à du code C/C++ exécuté sur CPU.
    Cela veut aussi dire que je ne savais pas vraiment comment rendre Rust rapide. Ce serait bien d’avoir des benchmarks de plusieurs implémentations Rust.
    L’implémentation de l’inférence LLM pourrait devenir le nouveau « Hello, world! » des programmeurs sérieux.
    https://github.com/gip/yllama.rs

    • J’ai moi aussi fait quelque chose dans ce genre, comme expérience « Hello, world ».
      https://github.com/crabml/crabml
      J’ai utilisé directement certaines instructions SIMD, et les performances semblaient pouvoir s’aligner sur celles de llama.cpp. Le point clé semble être l’utilisation de SIMD pour la multiplication de matrices quantifiées, ainsi que le recours à une boucle d’attente active plutôt qu’à des variables de condition pour répartir le travail entre threads.
      En revanche, je n’ai pas eu le temps de continuer à travailler sur l’inférence de modèles quantifiés sur GPU avec Vulkan, donc le projet n’a pas été mis à jour depuis un moment.
  • Le fait que Dioxus soit déjà utilisé est intéressant, et je me demande si WASM pourrait aussi entrer dans la roadmap
    Si l’on pouvait faire tourner dans le navigateur un LLM léger comme RWKV, cela ouvrirait une nouvelle catégorie de fonctionnalités côté navigateur, sans avoir à appeler une API SaaS

    • J’ai un peu expérimenté dans ce domaine
      https://github.com/maedoc/rwkv.js
      J’ai utilisé Rwkv.cpp compilé avec Emscripten, mais la partie tokenizer n’est pas encore vraiment résolue. Malgré tout, RWKV6 1.6B semble pouvoir être suffisamment exploitable pour un usage exclusivement dans un navigateur hors ligne
      Il n’a pas les capacités nécessaires pour du chat généraliste, mais pourrait être tout à fait suffisant pour des usages comme le RAG
    • La bibliothèque elle-même devrait pouvoir être compilée en WASM avec très peu de changements
      Les dépendances obligatoires rayon et wide prennent directement en charge WASM, et si l’on remplace le type Mmap de transformer.rs par &[u8], on peut aussi supprimer memmap2
      En revanche, RWKV a une architecture complètement différente, il faudrait donc tout réimplémenter, et il paraît très peu probable que cela entre dans la roadmap
  • Je me demande si toutes ces implémentations sont limitées au CPU
    La question est de savoir si, avec un bon GPU, il vaut mieux chercher une autre solution

    • Oui. Ce projet tourne sur CPU, donc il n’utilise pas le GPU pour le calcul
      Si vous voulez essayer un framework Rust prenant en charge le GPU, Candle https://github.com/huggingface/candle/tree/main vaut le détour
    • Tout est implémenté sur CPU et, du moins pour l’instant, il n’y a aucune accélération GPU
      Si l’objectif est vraiment d’exécuter des modèles, même en n’utilisant que le CPU, il vaut mieux prendre une alternative, à savoir llama.cpp. Ce projet ressemble davantage à un support pédagogique montrant comment les choses fonctionnent en interne lorsqu’on retire les couches complexes de l’écosystème
      Les LLM ont l’air magiques par leurs effets, mais du point de vue du code, ils sont assez simples
    • Selon le GPU, l’écart peut être de 10 à 20 fois
      Côté Rust, il existe des wrappers de llama.cpp comme mon llm_client, ainsi que des projets basés sur Candle comme mistral.rs et Kalosm
      Mon projet vise aussi à proposer une implémentation mistral.rs, mais je n’ai pas encore complètement migré depuis llama.cpp. Une implémentation entièrement en Rust présente de gros avantages, notamment des temps d’installation plus rapides. Pour l’instant, mon crate doit être cloné et compilé ; c’est automatisé sur macOS, Windows et Linux, mais cela ajoute environ une minute de compilation
    • Le CPU compte, mais le plus important est la bande passante mémoire
      Par exemple, une RTX 3090 dispose de près de 1 To/s de bande passante mémoire. Pour atteindre cela, il faudrait au minimum 12 canaux de la DDR5 la plus rapide au monde, au stade de preuve de concept
      Si l’on dispose d’un GPU dédié, utiliser une implémentation qui en tire parti est un tout autre monde. Les chiffres d’inférence LLM d’Apple Silicon sont impressionnants aussi parce que l’architecture de mémoire unifiée CPU-GPU offre une forte bande passante ; de mémoire, c’était autour de 400 Go/s
    • Cela dépend des cas. Les bons modèles sont grands et ont aussi de gros besoins en mémoire
      Même une 4090 n’a pas tant de mémoire que cela à l’échelle des LLM. Le GPU sera certes plus rapide, mais il est possible de ne pas pouvoir y charger de grands modèles
  • Je me demande quelle valeur cela apporte par rapport à llama.cpp

    • Cela peut être plus facile à intégrer à d’autres projets Rust
    • Comme il y a moins de fonctionnalités, la base de code est plus propre
  • C’est super, et félicitations aussi pour avoir créé une première bibliothèque Rust, mais pour un usage local sérieux, la prise en charge de Metal/CUDA est indispensable

    • Utiliser CUDA irait à l’encontre de l’objectif de ce projet, donc ce ne serait pas cohérent dès le départ
      Cela dit, sans être l’auteur principal mais en tant que contributeur, je mène des expérimentations pour obtenir un certain niveau d’accélération GPU avec wgpu. L’auteur principal veut garder la complexité sous contrôle, donc je ne sais pas jusqu’où cela ira réellement
  • L’enthousiasme de la communauté Rust à presque tout réécrire est intéressant et appréciable