coldpath : le build Ollama Windows-on-Arm est expédié avec l'unité matricielle éteinte. Correctif d'une ligne déposé en amont, 5,75x sur le traitement de prompt.
Je ne pouvais pas répondre à une question basique sur ma propre machine Arm : quand un LLM local tourne, utilise-t-il réellement l'unité matricielle de la puce, ou retombe-t-il silencieusement sur du code scalaire ? Alors j'ai écrit l'outil qui répond, l'ai pointé sur l'écosystème, et j'ai trouvé le runner LLM Windows-on-Arm le plus populaire froid. coldpath est un désassembleur AArch64 basé sur Capstone qui prouve qu'un binaire contient des instructions SME, i8mm et dotprod ; la trouvaille est que le build officiel win-arm64 d'Ollama en a zéro. Correctif d'une ligne déposé en amont (PR #17654), 5,75x récupéré sur le traitement de prompt mesuré en direct sur Azure Cobalt 100, gaté en CI comme GitHub Action réutilisable.

La question à laquelle je ne pouvais pas répondre
Je voulais savoir quelque chose de basique sur ma propre installation : quand je fais tourner un LLM quantifié sur une machine Arm, utilise-t-il réellement les instructions de multiplication matricielle de la puce, ou retombe-t-il silencieusement sur du code scalaire ? Je ne pouvais pas trouver comment y répondre. Les kernels matriciels Arm (i8mm, SME2, dot-product) sont sélectionnés à la compilation, donc un mauvais flag de build ne les rend pas lents, il les retire entièrement du binaire, et rien ne vous prévient. Le seul conseil disponible était d'attacher un profileur à du vrai silicium Arm et de lire des noms de symboles assembleur à la main. Cet écart est tout le projet : une question qu'un ingénieur compétent ne peut pas répondre aujourd'hui, rendue répondable, sans matériel Arm requis.
Pourquoi un scan statique est suffisant (et marche sur Arm mais pas sur x86)
ggml, XNNPACK et la plupart des bibliothèques de kernels Arm cuisent leurs chemins rapides dans chaque binaire à la compilation. Après la PR #10457 de llama.cpp (qui a retiré la détection ISA à l'exécution pour corriger une régression de ~15x), les intrinsèques i8mm/dotprod sont `#if`-gardés sur `__ARM_FEATURE_MATMUL_INT8` et `__ARM_FEATURE_DOTPROD`. Donc si le build a utilisé le mauvais `-march`, le chemin rapide n'est pas simplement sauté : il est compilé dehors, physiquement absent du binaire. Désassembler `.text` et regarder est donc décisif. Deux propriétés rendent ça solide. D'abord, AArch64 est à largeur fixe, instructions de 4 octets sur une grille de 4 octets, donc un mot de données incrusté dans `.text` (un pool littéral, une table de sauts) est local à ses 4 octets et ne peut jamais cascader dans le code environnant. Un balayage linéaire x86 se désynchroniserait et corromprait tout en aval ; sur Arm ce n'est pas possible. Ensuite, la détection lit les opérandes de registre (tuiles ZA pour SME, Z/P pour SVE) parce que Capstone laisse `insn.groups` vide pour SVE comme pour SME, donc la détection par groupe rapporte silencieusement zéro, exactement la classe de faux négatifs que cet outil existe pour attraper.

Ce que le chemin froid coûte, en dollars, mesuré en direct
La partie honnête. Chaque ligne est construite `GGML_NATIVE=OFF` avec un `-march` épinglé pour isoler l'effet ISA ; seul `-march` change. Cold armv8-a (la base sur laquelle un build portable retombe, le build Windows d'Ollama) exécute la préconstruction Qwen2.5-0.5B Q4_0 à ~95 tok/s. Le correctif d'une ligne que j'ai déposé (armv8.2-a+dotprod, sûr sur chaque appareil Arm expédié) tourne à ~545, une accélération 5,75x, environ 0,08 $ par million de tokens de prompt contre une base de 0,45 $. armv8.6-a+i8mm+bf16 tourne à ~660, 6,9x, environ 0,065 $. La génération s'améliore d'environ 2,3x. Les dollars utilisent un tarif on-demand Arm-cloud d'exemple (Graviton4 c8g, 0,0385 $/vCPU-heure), donc seule cette colonne suppose un prix ; les tok/s et le 5,75x sont mesurés sur le runner Neoverse N2 4-vCPU et se régénèrent depuis CI à chaque push.


Le correctif amont : une ligne, déposée comme Ollama PR #17654
La cause est une seule variable CMake manquante dans le `llama/server/CMakePresets.json` d'Ollama. `GGML_CPU_ALL_VARIANTS` est éteint (donc le build ne peut pas dlopen le meilleur de plusieurs variantes ISA à l'exécution), et `GGML_CPU_ARM_ARCH` n'est pas défini, donc le build retombe sur armv8-a de base et chaque intrinsèque matriciel est `#if`-é dehors. Ajouter `"GGML_CPU_ARM_ARCH": "armv8.2-a+dotprod"` récupère le dot-product sur chaque appareil Arm expédié, y compris les puces classe Cortex-A76 des laptops Windows-on-Arm qui n'ont pas i8mm. C'est pourquoi la PR expédie intentionnellement dot-product seulement, pas la ligne i8mm 6,9x : le dot-product est sûr sur chaque coeur, i8mm ne l'est pas.

Comme gate CI : un build froid ne rejoint jamais votre flotte
Pointez la Action réutilisable sur votre propre sortie de build, où chaque binaire devrait être hot. `coldpath --require i8mm ./build/` sort non-zéro si un binaire expédié manque les instructions matricielles, donc la pull request échoue avant que le build froid n'atteigne une flotte Arm. Quand à la place vous scannez toute une archive de release tierce, les kernels matmul vivent généralement dans une lib backend (ex. `ggml-cpu.dll`) tandis que les exécutables `llama-*`/`ollama` à côté sont des shims lanceurs minces qui ne portent pas de kernels, donc une gate stricte échouerait sur les shims. Pour ce cas, jugez le set par son meilleur membre avec `--any` (CLI) ou `any: true` (Action). Le même flag couvre un build multi-variantes qui dlopen le meilleur de plusieurs libs single-ISA à l'exécution, donc le set est noté sur la variante qui charge réellement, pas sur son fallback armv8.0.

Vérité terrain, et limites honnêtes
L'outil est validé contre une vérité terrain qu'il n'a pas écrite. La release linux-arm64 officielle de llama.cpp expédie huit backends `ggml-cpu` avec le niveau ISA dans le nom de fichier, et `scripts/verify_ladder.py` reproduit cet escalier de huit lignes exactement (armv8.0 a zéro de tout, armv8.2 obtient 1 184 dotprod, armv8.2_3 ajoute 10 735 SVE, armv8.6 ajoute 402 i8mm, et ainsi de suite). Le wheel aarch64 d'ONNX Runtime est un contrôle positif prouvant qu'un zéro signifie une vraie absence, avec 469 SME, 800 i8mm, et 1 642 dotprod. La portée est déclarée d'entrée : sur Apple Silicon, la matmul peut passer par l'unité Accelerate/AMX, qu'un désassembleur ne peut pas voir, donc le titre est cadré à Linux/Neoverse, Windows-on-Arm, et Android où le chemin ISA est le seul chemin. coldpath rapporte ce qui est présent dans `.text` ; la présence égale ce qui s'exécute pour ggml (dispatch à la compilation) et est un indicateur d'expédition prouvée pour les libs à dispatch runtime.

coldpath: The Arm AI Ecosystem Ships with the Matrix Unit Off. I Wrote the Tool that Finds It, Filed the One-Line Fix Upstream (Ollama PR #17654), and Gated It in CI.
Voir le projet