Skip to content
/ airelien.dev
Go back
Aurélien AMSELLEM

Lucebox sur Olares One — Épisode 6 : On lit le code source de HAMi-core et on trouve 6 bugs

NO_VMM ne fix rien. Le bug `Illegal device id` revient à chaque run. Il faut lire le code de HAMi-core. Et ce qu'on trouve, c'est pas un bug — c'est un pattern systémique présent dans 6 hooks différents.

Épisode 5NO_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 :

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 :

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 :

FichierFonctionConséquence
cuda/memory.ccuMemCreateIndex OOB dans shared region via add_chunk_only
allocator/allocator.coom_check (branche dev == -1)Lit get_current_device_memory_limit(d) avec d invalide
allocator/allocator.cadd_chunkOOB dans oom_check puis add_gpu_device_memory_usage
allocator/allocator.cremove_chunkOOB dans rm_gpu_device_memory_usage
allocator/allocator.cremove_chunk_asyncOOB dans rm_gpu_device_memory_usage
allocator/allocator.cadd_chunk_asyncOOB 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

  1. Initialiser dev = -1 au début de chaque site
  2. Vérifier le return code de cuCtxGetDevice
  3. Si fail, bail out gracieusement — skip le tracking memory mais laisser l’allocation/free CUDA sous-jacente continuer normalement
  4. Tighten set_current_device_memory_limit pour return early sur dev invalide 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.

Share this post on:

Commentaires