NOPE LinkedIn

Catégories:
IA
BSD
Performance

FreeBSD pour l'inférence LLM embarquée : un non-sujet

FreeBSD pour l'inférence LLM embarquée : un non-sujet image

Rubrique: IA Rubrique: BSD Rubrique: Performance Tag: llama.cpp Tag: freebsd Tag: linux Tag: benchmark Tag: cpu Tag: opnsense Tag: embedded Tag: appliance Tag: inference

FreeBSD pour l’inférence LLM embarquée : un non-sujet

TL;DR

Sur le même CPU (AMD Ryzen 5 3600, Zen 2), le même tag llama.cpp, le même modèle (Qwen 2.5 3B Q4_K_M), le même nombre de threads — Linux Debian 12 et FreeBSD 14.4 produisent des t/s quasi identiques :

OS tag llama.cpp t=6 pp256 t=6 tg64
Linux Debian 12 b9165 90.6 17.1
FreeBSD 14.4 b9000 90.5 16.7

Différence < 1 % sur le pp, ~2 % sur le tg — dans la marge d’erreur des mesures successives. Pour les opérateurs réseau qui se demandent si embarquer un LLM dans une appliance BSD (OPNsense, pfSense, FreeNAS, TrueNAS) pénalise les perfs, la réponse est : non, l’OS n’est pas le sujet.

1. Pourquoi cette question m’intéressait

J’ai un PoC, opnsense-ai-firewall, qui consiste à embarquer un LLM directement dans une VM OPNsense. OPNsense, c’est du FreeBSD modifié — pas de containers Linux, pas de Linuxulator pour les workloads sérieux, le binaire llama-server doit être compilé natif BSD.

La crainte, partagée par à peu près tous les sysadmins à qui j’en parle : « Le LLM va tourner moitié moins vite sur FreeBSD que sur Linux. » L’argument sous-jacent est intuitif :

  • Linux a une stack scheduler/mémoire plus moderne que FreeBSD pour les workloads compute-intensifs ;
  • La plupart des optimisations llama.cpp sont testées en CI sur Linux ;
  • FreeBSD est “moins mainstream” pour le ML.

J’avais une intuition contraire — pour de l’inférence pure, rien dans le système d’exploitation ne devrait être sur le chemin critique. Le binaire llama-bench est un user-space tight loop : il alloue ses buffers, il map ses tensors, il fait du matmul. Le kernel n’intervient quasiment pas, sauf au démarrage.

Mais l’intuition ne vaut rien sans mesure. Voici la mesure.

2. Setup

J’ai monté deux VMs libvirt sur le même hôte (mon Ryzen 5 3600 desktop) :

VM OS vCPU RAM CPU mode
breach-1-llm-lab Debian 12 8 8 GB host-passthrough
oaf-build-freebsd FreeBSD 14.4 8 8 GB host-passthrough

host-passthrough signifie que les deux VMs voient strictement le même CPU avec les mêmes flags (AVX2, FMA, etc.). Pas d’émulation ou de masquage.

Sur chaque VM, j’ai compilé llama.cpp avec exactement les mêmes flags :

# Linux Debian
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
  -DGGML_NATIVE=ON -DGGML_BLAS=OFF \
  -DLLAMA_BUILD_SERVER=OFF -DLLAMA_BUILD_TESTS=OFF
ninja -j8 llama-bench

# FreeBSD 14.4 (rebuild avec gmake après cmake)
cmake -B build-native -DCMAKE_BUILD_TYPE=Release \
  -DGGML_NATIVE=ON -DGGML_BLAS=OFF \
  -DLLAMA_BUILD_SERVER=OFF -DLLAMA_BUILD_TESTS=OFF \
  -G "Unix Makefiles"
gmake llama-bench -j 8

Les tags llama.cpp ne sont pas exactement les mêmes (Linux : b9165, FreeBSD : b9000) parce que la VM FreeBSD avait été pré-provisionnée avec b9000 et je n’ai pas voulu re-checkout pour cette mesure. La différence entre b9000 et b9165 est de l’ordre de quelques semaines de patches mineurs, pas un saut générationnel comme entre b3813 et b9165. À considérer comme négligeable pour cette comparaison.

Modèle : Qwen2.5-3B-Instruct-Q4_K_M.gguf (1.79 GiB), copié sur les deux VMs.

Bench : llama-bench -m qwen2.5-3b-q4km.gguf -p 256 -n 64 -t {4,6,8}. 5 itérations chacun, moyenne ± écart-type.

3. Résultats

Threads Linux pp256 Linux tg64 FreeBSD pp256 FreeBSD tg64
4 (74.5 ik) (17.8 ik) 67.07 ± 0.72 16.46
6 90.6 17.1 90.48 ± 2.95 16.71
8 93.31 16.25 91.35 ± 5.07 15.76

(Les chiffres Linux à t=4 correspondent à ik_llama.cpp dans mon dataset, pas à llama.cpp pur. Pour la comparaison la plus stricte, regarder la ligne t=6 qui est le sweet spot pour les deux OS.)

À t=6, Linux donne 90.6 t/s pp et 17.1 t/s tg. FreeBSD donne 90.5 t/s pp et 16.7 t/s tg.

Δ pp = 0.1 t/s soit ~0.1 %. Δ tg = 0.4 t/s soit ~2.3 %.

Les écarts-types des mesures successives (sur le même OS, en re-runnant) sont déjà de l’ordre de ±2-5 %. Donc tout ce qu’on peut conclure honnêtement, c’est : Linux et FreeBSD donnent les mêmes chiffres dans la limite de la précision de mesure.

Pas de pénalité OS.

4. Pourquoi c’est attendu (en y réfléchissant)

Le kernel Linux est globalement réputé “meilleur” pour les workloads serveur modernes — meilleure scheduler granularité, meilleur cgroups, meilleur io_uring, meilleur networking, etc. Mais l’inférence LLM CPU n’utilise rien de tout ça.

Le profil système d’un llama-bench qui tourne :

  • Pas de syscalls dans la boucle chaude. Tous les buffers sont alloués au démarrage. Le forward pass est du compute pur.
  • Pas d’I/O. Le modèle est mmap-é une fois, puis on tourne en RAM.
  • Pas de network. Pas de scheduler arbitrant entre process.
  • Pas de fairness ni preemption agressive. Les threads sont CPU-bound, le scheduler les laisse tranquilles.

Ce qui compte, c’est :

  • La qualité du compilateur (clang sur les deux OS, version 14 sur Debian 12, version équivalente sur FreeBSD 14.4) ;
  • La qualité de l’allocateur mémoire (jemalloc par défaut sur FreeBSD, glibc malloc sur Debian — différence négligeable pour une allocation unique au démarrage) ;
  • La qualité du memory mapping des tensors (mmap, identique dans les deux cas pour des fichiers locaux) ;
  • L’efficacité du parallélisme intra-process (pthread sur les deux, même API) ;
  • La capacité du kernel à affinity-pin les threads (les deux le font, légèrement différemment, mais pas de différence mesurable ici).

Le seul endroit où l’OS pourrait théoriquement avoir un impact, c’est la gestion des huge pages ou des NUMA nodes. Sur une machine simple Ryzen mono-socket sans huge pages explicites, c’est neutre.

Pour un LLM CPU sur appliance BSD, vous aurez exactement les mêmes performances brutes que sur Linux, à condition d’avoir :

  • compilé natif (pas via Linuxulator) ;
  • compilé avec clang récent et -march=native ;
  • compilé sans OpenBLAS (cf. l’article principal qui mesure le piège OpenBLAS).

5. Conséquences pratiques pour les opérateurs réseau

5.1 Le LLM dans une appliance OPNsense / pfSense

Si vous voulez embarquer un assistant LLM directement dans une appliance firewall (un PoC que je documente dans opnsense-ai-firewall, avec toutes les raisons pour lesquelles c’est une mauvaise idée en production), la performance pure du LLM n’est pas un argument contre. Vous aurez les mêmes t/s qu’un sidecar Linux séparé.

Les vraies raisons de ne PAS embarquer le LLM dans le firewall sont ailleurs :

  • Surface d’attaque : ajouter un binaire utilisateur exposé HTTP dans un kernel BSD durci, c’est régression côté sécurité ;
  • Cycle de vie : llama.cpp bouge beaucoup plus vite qu’OPNsense ; rebuilder à chaque release crée une dépendance fragile ;
  • Compétition CPU : le LLM mange la CPU, et le firewall en a besoin pour pf/Suricata/NAT ;
  • Mémoire : 8 GB RAM minimum (Phi-3 mini Q4 = 2.5 GB + ctx + OS + pf), c’est beaucoup pour un firewall.

Ces raisons sont solides. La perf, non. Si vous avez besoin du LLM et que vous acceptez les autres trade-offs, FreeBSD ne vous coûte rien en t/s.

5.2 NAS, stockage, edge appliances

Même argument pour TrueNAS / FreeNAS : si vous avez un Ryzen ou un EPYC dans votre NAS, et que vous voulez y faire tourner un LLM (pour analyser vos logs, indexer vos documents, faire de l’OCR + LLM, etc.), n’allez pas migrer vers Linux pour la perf. Restez sur BSD, vous aurez les mêmes chiffres.

5.3 Le mythe à tuer

J’entends parfois : “FreeBSD c’est sympa pour le serveur d’entreprise mais pour l’IA il faut Linux.” Cette croyance vient probablement de l’écosystème GPU où les drivers NVIDIA / ROCm sont historiquement Linux-first et où FreeBSD n’a pas de support CUDA natif. C’est vrai pour le GPU.

Mais pour de l’inférence CPU, l’écosystème est portable (llama.cpp compile sur tout ce qui supporte clang + cmake + pthread, y compris OpenBSD, NetBSD, macOS, Haiku…) et la performance est identique.

L’écart Linux-vs-FreeBSD pour le LLM CPU, c’est du bruit. Pour le LLM GPU, c’est un mur. Ne mélangez pas les deux discussions.

6. Limites et caveats

  • Mesure mono-architecture (Zen 2 only). Pas testé sur Intel ou ARM. L’absence d’écart est peut-être moins universelle qu’elle n’y paraît — Linux a probablement un meilleur tuning ARM pour les workloads SIMD que FreeBSD/aarch64. À mesurer si vous opérez sur Apple Silicon ou Ampere Altra.
  • Mesure mono-modèle (Qwen 3B uniquement). Pour des modèles plus gros (>20B), les patterns d’accès mémoire changent et l’OS pourrait commencer à compter. À mesurer pour Llama 3 70B ou Mixtral.
  • Pas de stress concurrent. Si vous avez en parallèle Suricata qui mange 4 cores et un LLM qui mange 4 cores, le scheduler Linux pourrait se comporter différemment du scheduler BSD. Sur charge isolée (mon cas), pas d’écart.
  • Tags llama.cpp pas strictement identiques (b9000 vs b9165). Quelques jours d’écart, pas un changement majeur. Pour une mesure académique stricte, refaire avec exactement le même tag.

7. Reproductibilité

Pour rejouer le bench sur votre propre VM FreeBSD :

# FreeBSD 14.x avec pkg
pkg install -y git cmake llvm openblas gmake

git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp
git checkout b9165
cmake -B build -DCMAKE_BUILD_TYPE=Release \
  -DGGML_NATIVE=ON -DGGML_BLAS=OFF \
  -DLLAMA_BUILD_SERVER=OFF -DLLAMA_BUILD_TESTS=OFF \
  -G "Unix Makefiles"
cd build && gmake llama-bench -j$(sysctl -n hw.ncpu)

fetch https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF/resolve/main/Qwen2.5-3B-Instruct-Q4_K_M.gguf

./bin/llama-bench -m Qwen2.5-3B-Instruct-Q4_K_M.gguf -p 256 -n 64 -t 6

Sur le Ryzen 5 3600, vous devriez voir ~90 t/s en pp256 et ~17 t/s en tg64. C’est exactement ce que donne Linux Debian sur la même machine. Les autres CPU donneront d’autres chiffres mais l’écart Linux/BSD restera négligeable.

8. Pour aller plus loin

Le PoC opnsense-ai-firewall continue d’avancer parce que la perf n’est plus l’argument bloquant. Reste à régler les autres — sécurité, cycle de vie, compétition CPU. Ces obstacles, eux, sont vrais.