Le problème : « le GPU est à 30 %, pourquoi ? »

C’est l’une des questions les plus frustrantes en production d’inférence. La carte coûte cher, la charge est là, et nvidia-smi affiche 30 % d’occupation. Le GPU n’est pas le goulot — quelque chose en amont ne le nourrit pas assez vite.

Le réflexe est de regarder le GPU. C’est rarement la bonne piste. Un GPU sous-utilisé est, dans l’immense majorité des cas, un problème côté hôte : le CPU, la mémoire, le disque ou le réseau qui n’arrivent pas à suivre. Et le côté hôte, c’est précisément ce qu’eBPF sait rendre visible.

Ce qu’eBPF est, en une phrase

eBPF permet d’exécuter de petits programmes vérifiés à l’intérieur du noyau Linux, attachés à des événements : appels système, tracepoints, sondes sur des fonctions noyau (kprobes) ou utilisateur (uprobes). Le programme observe l’événement, agrège, et renvoie le résultat — sans modifier l’application surveillée, sans la recompiler, sans la redémarrer.

Pour une stack d’inférence, c’est l’outil idéal : on ne veut pas instrumenter vLLM ou llama.cpp, on veut savoir ce que le système leur fait subir.

Ce qu’eBPF voit — et ce qu’il ne voit pas

C’est la distinction la plus importante de cet article, et celle qu’on rate le plus souvent.

CoucheOutilCe qu'on y voit
Ordonnancement CPUeBPF / perfLatence de run-queue, préemption
Mémoire hôteeBPFPage faults, allocations, mmap
I/O disque & réseaueBPFLatence, débit, blocages
Frontière hôte → GPUeBPFAppels ioctl au driver, leur latence
Intérieur du GPUNsight / rocprofOccupation SM, kernels, HBM
Tableau 1 — Qui voit quoi : eBPF couvre l'hôte, les profileurs constructeur couvrent le GPU.

Les quatre angles morts d’une stack d’inférence

Quand le GPU est affamé, la cause est presque toujours dans l’un de ces quatre endroits — tous visibles par eBPF.

L’ordonnancement CPU. Le thread qui prépare les batches attend dans la run-queue au lieu de tourner. Le GPU attend ce thread. Une latence de run-queue élevée est une cause classique et invisible sans traçage.

Les page faults. Un modèle chargé en mmap — le mode normal de llama.cpp — pagine depuis le disque à la demande. Sous pression mémoire, les pages sont évincées puis rechargées : chaque page fault majeur est un accès disque qui gèle le calcul. eBPF compte ces fautes par processus.

L’I/O disque et réseau. Pour un service exposé, la latence réseau d’admission des requêtes peut dépasser le temps de calcul. Pour un démarrage, c’est la lecture du modèle depuis le stockage. Dans les deux cas, eBPF mesure la latence réelle, pas la latence supposée.

La frontière hôte → GPU. Le driver NVIDIA ou AMD est piloté depuis l’espace utilisateur par des appels ioctl. Tracer leur fréquence et leur latence révèle si le GPU passe son temps à attendre des commandes — un symptôme de côté hôte trop lent.

En pratique : bpftrace, perf, ftrace

La boîte à outils est mûre et tient en trois noms. bpftrace pour des sondes ad hoc en une ligne. perf pour l’échantillonnage CPU et les flame graphs. ftrace pour le traçage fin de fonctions noyau. La collection BCC ajoute des outils prêts à l’emploi.

Un exemple concret : mesurer la latence d’ordonnancement — combien de temps un thread attend dans la run-queue avant de tourner.

bpftrace runqlat.bt Bash
# Latence de run-queue : un thread est-il prêt mais en attente du CPU ?
# Un GPU "à 30 %" est souvent un thread hôte coincé ici.
bpftrace -e '
tracepoint:sched:sched_wakeup { @qt[args->pid] = nsecs; }
tracepoint:sched:sched_switch /@qt[args->next_pid]/ {
    @runq_us = hist((nsecs - @qt[args->next_pid]) / 1000);
    delete(@qt[args->next_pid]);
}'

L’histogramme @runq_us montre la distribution des temps d’attente en microsecondes. Si la queue de votre processus d’inférence s’étale vers les millisecondes, le GPU n’est pas en cause — l’ordonnanceur l’est. La même logique s’applique aux page faults, à l’I/O, aux ioctl : une sonde, un histogramme, une réponse.

La règle : eBPF pour l’hôte, les profileurs constructeur pour le GPU

Inverser cet ordre fait perdre des heures : profiler les kernels CUDA d’un GPU qui, de toute façon, attend son CPU, ne mène nulle part.

Conclusion

L’inférence LLM se raisonne souvent comme un problème de GPU. En production, c’est tout aussi souvent un problème de système — et le système ne se devine pas, il se trace. eBPF rend visibles les quatre angles morts qui affament un GPU : ordonnancement, page faults, I/O, frontière avec le driver. Il ne remplace pas les profileurs constructeur, il les précède : on diagnostique l’hôte d’abord, le GPU ensuite. C’est la couche d’observabilité que les abstractions d’inférence ne fournissent pas — et c’est là qu’on récupère les pourcents perdus.

Sources et méthode

eBPF, bpftrace, perf et ftrace : documentation publique du noyau Linux et des projets bpftrace et BCC. L’exemple runqlat.bt reprend la logique de l’outil runqlat de la collection bpftrace/BCC ; il est volontairement simplifié pour la lisibilité et illustre le principe, il ne reproduit pas toutes les options de l’outil complet. La frontière entre observabilité hôte (eBPF) et profilage GPU (Nsight Systems côté NVIDIA, ROCm rocprof côté AMD) est une propriété d’architecture, documentée par les constructeurs concernés. Article de synthèse — il ne rapporte pas de mesure de banc, mais une méthode.