Épisode 5 — NO_VMM ne suffit pas, le pod crashe toujours avec un Illegal device id aléatoire. Je sors de la zone “tweak des flags CMake” et je rentre dans “lire le code source du composant qui crashe”.
Le repo
HAMi est en deux repos :
Project-HAMi/HAMi— le device plugin Kubernetes (Go)Project-HAMi/HAMi-core— la liblibvgpu.soqui est LD_PRELOAD dans les pods (C)
Le crash vient de la lib C. Je clone HAMi-core :
git clone https://github.com/Project-HAMi/HAMi-core
cd HAMi-core
grep -rn "Illegal device id" src/
Bingo, premier hit :
// src/multiprocess/multiprocess_memory_limit.c
int set_current_device_memory_limit(const int dev, size_t newlimit) {
ensure_initialized();
if (dev < 0 || dev >= CUDA_DEVICE_MAX_COUNT) {
LOG_ERROR("Illegal device id: %d", dev);
}
LOG_INFO("dev %d new limit set to %ld", dev, newlimit);
region_info.shared_region->limit[dev] = newlimit; // ← OOB write
return 0;
}
Premier truc qui me choque : la fonction logge l’erreur et continue, écrivant dans region_info.shared_region->limit[dev] avec un dev invalide. C’est un OOB write dans la mémoire partagée. Silencieux. Une autre app sur le système peut être affectée.
Mais c’est pas la cause racine — la cause c’est d’où vient le dev invalide. Donc je remonte.
Le pattern coupable
set_current_device_memory_limit est appelée depuis add_chunk_only qui est appelée depuis plusieurs hooks dans src/cuda/memory.c et src/allocator/allocator.c. Et dans tous ces hooks, je vois ce pattern :
CUdevice dev; // ← uninitialized
cuCtxGetDevice(&dev); // ← return value ignored
if (oom_check(dev, size)) { ... } // ← dev forwarded
add_chunk_only(*handle, size, dev); // ← stored as device id
Le bug est là. cuCtxGetDevice peut retourner CUDA_ERROR_INVALID_CONTEXT si le thread courant n’a pas pushed son context CUDA — ce qui arrive tout le temps en async / multi-thread / early-init :
- Un thread qui appelle un hook
cuMem*avantcuCtxSetCurrent(multi-thread allocator) - Init paths où on alloue avant que le context global soit attaché
- CUDA Graphs capture qui bypass le context par design
Quand cuCtxGetDevice échoue, le code ignore le return code et dev reste avec sa valeur stack random. C’est notre Illegal device id: -644371744.
Combien de sites ?
Je grep tout :
$ grep -n "cuCtxGetDevice" src/cuda/*.c src/allocator/*.c
src/allocator/allocator.c:39: cuCtxGetDevice(&d); // ignored
src/allocator/allocator.c:106: cuCtxGetDevice(&dev); // ignored
src/allocator/allocator.c:126: cuCtxGetDevice(&dev); // ignored
src/allocator/allocator.c:172: cuCtxGetDevice(&dev); // ignored
src/allocator/allocator.c:229: cuCtxGetDevice(&dev); // ignored
src/allocator/allocator.c:249: cuCtxGetDevice(&dev); // ignored
src/allocator/allocator.c:276: cuCtxGetDevice(&dev); // ignored
src/cuda/memory.c:592: if (do_oom_check && cuCtxGetDevice(&dev) != CUDA_SUCCESS) {
8 occurrences. Une seule (src/cuda/memory.c:592) check le return code. Les 7 autres l’ignorent. C’est pas un bug ponctuel, c’est un pattern systémique.
Décortiquons ce que chaque site fait avec un dev corrompu :
| Fichier | Fonction | Conséquence |
|---|---|---|
cuda/memory.c | cuMemCreate | Index OOB dans shared region via add_chunk_only |
allocator/allocator.c | oom_check (branche dev == -1) | Lit get_current_device_memory_limit(d) avec d invalide |
allocator/allocator.c | add_chunk | OOB dans oom_check puis add_gpu_device_memory_usage |
allocator/allocator.c | remove_chunk | OOB dans rm_gpu_device_memory_usage |
allocator/allocator.c | remove_chunk_async | OOB dans rm_gpu_device_memory_usage |
allocator/allocator.c | add_chunk_async | OOB dans oom_check puis cuDeviceGetMemPool qui peut crasher avec un device id négatif |
Six sites critiques. Un seul (cuMemCreate ligne 592) a déjà la vérification — et juste partiellement (seulement quand do_oom_check == true).
Pourquoi tout casse en même temps
Maintenant je comprends pourquoi NO_VMM n’a rien changé. Désactiver VMM pousse ggml à utiliser cudaMalloc au lieu de cuMemCreate. Mais cudaMalloc finit aussi par appeler les hooks d’allocator.c (add_chunk, remove_chunk, etc.) — qui ont exactement le même bug. Le pattern est partout dans le code.
Donc tant que mon thread llama-server allouait avant d’avoir un context attaché — ce qu’il fait pendant l’init du KV cache, sur sm_120, parce que la séquence d’init est légèrement différente — cuCtxGetDevice échoue et on tombe dans le pattern. Quel que soit le path d’allocation choisi.
Pour vraiment fixer ça, il faut patcher les 6 sites.
Le plan
- Initialiser
dev = -1au début de chaque site - Vérifier le return code de
cuCtxGetDevice - Si fail, bail out gracieusement — skip le tracking memory mais laisser l’allocation/free CUDA sous-jacente continuer normalement
- Tighten
set_current_device_memory_limitpour return early surdevinvalide au lieu d’OOB write
C’est un patch de ~50 lignes. Et c’est upstream qu’il faut le pousser, pas chez moi.
Épisode 7 — Issue #187 et PR #188 chez HAMi-core. À très vite !
Disclosure — Tous les benchmarks de ce post tournent sur mon propre Olares One. Si le contenu vous a été utile et que vous envisagez d’en acheter un, commander via ce lien de parrainage vous donne 400 $ de réduction (3 599 $ au lieu de 3 999 $) et me rapporte 200 $. Je le mentionne par transparence — et oui, accessoirement, ça aide à faire vivre le blog (hébergement, domaine, et le temps que je passe à écrire ici). Lien valable jusqu’à fin juin 2026 environ.