Contexte et problème

Avant l’ajout du noyau, llama.cpp traitait les invites (prompts) plusieurs fois plus lentement sur les CPU Arm que sur les x86 lorsqu’il s’agissait de modèles quantifiés à faible précision. La cause principale était la routine tiled K‑quant matmul, introduite en septembre 2026 (pull‑request #27851), qui ne disposait que d’interne kernels pour AVX‑512 VNNI, AVX2 et AVX. Sur Arm, les formats Q3_K, Q2_K et la famille IQ étaient obligés de décompresser les poids à chaque produit scalaire, ce qui alourdissait fortement le calcul.

Implémentation du noyau NEON

Le dépôt silicon-forge-arm-neon-study ajoute un kernel interne dédié aux processeurs Arm, utilisant l’instruction SDOT du jeu d’instructions NEON. Le patch compte 164 lignes ajoutées dans deux fichiers de llama.cpp v0.9.0 :

#if defined(__aarch64__) && defined(__ARM_FEATURE_DOTPROD)
// activation du kernel NEON pour les architectures Arm avec SDOT
#endif

Deux choix de conception sont mis en avant :

Débiaisage à la charge : le code de poids, moins le biais, tient dans un octet signé. Le kernel soustrait le biais lors du chargement, ce qui permet d’obtenir directement la somme corrigée en arithmétique entière.

Interleaving du côté prompt : les 16 lignes d’une bande sont intercalées en place, reproduisant la disposition locale du kernel VNNI. Une instruction SDOT par lane ajoute quatre sommes partielles pour quatre lignes différentes, évitant toute réduction supplémentaire. Ainsi, quatre lignes de poids et seize lignes de prompt sont accumulées dans seize registres par passe.

Benchmarks et gains mesurés

Les mesures ont été réalisées sur trois générations de cœurs serveur Arm (AWS Graviton2, Graviton3, Graviton4) avec un modèle Qwen2.5 1.5 B, 4 threads, et une invite de 512 tokens. Le tableau principal indique un gain maximal de 6,5× pour le format IQ3_M sur le Neoverse N1. Les formats déjà dotés d’un kernel Arm (Q4_K_M, Q5_K_M, Q6_K, Q8_0) n’ont montré aucune variation.

Les tests unitaires (test-tiled-mulmat) valident 15 / 15 cas sur Neoverse N1, V1 et V2, et 88 / 88 cas en mode fuzz. Aucun bug de correction n’a été détecté dans la revue ligne par ligne. La vitesse de génération (inférence) reste identique, la routine n’étant utilisée que pour le pré‑traitement du prompt.

Limites et perspectives

Malgré le gain, les performances Arm restent inférieures à la référence x86 ; les facteurs limitants ne sont pas encore profilés. Les mesures ont été limitées à 4 et 8 threads ; on anticipe une diminution du facteur d’accélération au‑delà de 16 threads, mais cela n’a pas été testé. Aucun benchmark n’a été réalisé sur les appareils mobiles, Apple Silicon, Windows on Arm, ni sur des cœurs à petite cache ou à contraintes thermiques.

Le kernel exploite uniquement le NEON 128 bits ; les extensions SVE et I8MM n’ont pas été exploitées. Le code n’a pas été soumis à des builds avec sanitizers, ni revu par un ingénieur kernel humain. Enfin, le patch n’est pas intégré à la branche officielle de llama.cpp et nécessite un rebase sur la future organisation du code (pull‑request #30200).