Épisode 6 — diag complet : 6 hooks de HAMi-core ont tous le même bug d’uninitialized dev après cuCtxGetDevice ignored. Le fix est clair : initialiser, vérifier le return code, bail out gracieux si fail.
Maintenant, deux options :
-
Patcher la lib en local sur mon Olares (un seul fichier
.soà compiler, scp sur le host, restart les pods GPU). Solution rapide, ne bénéficie qu’à moi. -
Pousser le fix upstream chez
Project-HAMi/HAMi-core. Solution propre, profite à toute la communauté HAMi.
Évidemment je fais les deux, en commençant par le PR upstream. Bon karma open source.
L’issue
Premier réflexe avant un PR : ouvrir une issue qui décrit le bug en détail, avec un repro minimal. Ça donne aux maintainers le contexte pour reviewer la PR ensuite.
Le repro est court. Un main C qui appelle cuMemCreate avec un prop.location.type = CU_MEM_LOCATION_TYPE_HOST_NUMA — c’est exactement le path que ggml prend pour son virtual address pool :
#include <stdio.h>
#include <cuda.h>
int main(void) {
cuInit(0);
CUdevice device; cuDeviceGet(&device, 0);
CUcontext ctx; cuCtxCreate(&ctx, 0, device);
CUmemAllocationProp prop = {0};
prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
prop.location.type = CU_MEM_LOCATION_TYPE_HOST_NUMA;
prop.location.id = 0;
size_t granularity;
cuMemGetAllocationGranularity(&granularity, &prop, CU_MEM_ALLOC_GRANULARITY_MINIMUM);
size_t size = ((1<<20) + granularity - 1) / granularity * granularity;
CUmemGenericAllocationHandle handle;
CUresult res = cuMemCreate(&handle, size, &prop, 0);
printf("cuMemCreate returned %d\n", res);
cuCtxDestroy(ctx);
return 0;
}
nvcc -lcuda repro.c -o repro, run dans un pod managé par HAMi, et [HAMI-core ERROR ...]: Illegal device id: <random> apparaît immédiatement.
Issue : Project-HAMi/HAMi-core#187. Texte clair, repro complet, root cause identifiée, fix suggéré inline.
Le PR
Ensuite je fork le repo, branch fix/cumemcreate-uninit-dev, applique le fix.
Premier commit — cuMemCreate (le hook spécifique qui m’a planté) :
// avant
CUdevice dev; // uninitialised
int do_oom_check = (prop->location.type == CU_MEM_LOCATION_TYPE_DEVICE);
if (do_oom_check && cuCtxGetDevice(&dev) != CUDA_SUCCESS) {
dev = prop->location.id;
}
// ...
if (res == CUDA_SUCCESS) {
add_chunk_only(*handle, size, dev); // peut être uninit
}
// après
CUdevice dev = prop->location.id; // initialisé up-front
int do_oom_check = (prop->location.type == CU_MEM_LOCATION_TYPE_DEVICE);
if (do_oom_check && cuCtxGetDevice(&dev) != CUDA_SUCCESS) {
dev = prop->location.id;
}
// ...
if (res == CUDA_SUCCESS && do_oom_check) { // skip non-DEVICE
add_chunk_only(*handle, size, dev);
}
Plus le tightening de set_current_device_memory_limit pour return early :
if (dev < 0 || dev >= CUDA_DEVICE_MAX_COUNT) {
LOG_ERROR("Illegal device id: %d", dev);
return -1; // était manquant
}
Deuxième commit — les 5 sites de allocator.c. Pattern identique partout :
// avant
CUdevice dev;
cuCtxGetDevice(&dev);
if (oom_check(dev, size)) ...
// après
CUdevice dev = -1;
if (cuCtxGetDevice(&dev) != CUDA_SUCCESS) {
LOG_WARN("add_chunk: cuCtxGetDevice failed, skipping memory tracking");
return CUDA_SUCCESS;
}
if (oom_check(dev, size)) ...
Stratégie : si on ne sait pas sur quel device l’allocation a lieu, on ne la track pas au lieu de tracker n’importe quoi et corrompre la shared memory. L’allocation CUDA sous-jacente continue normalement, simplement HAMi ne la compte pas dans ses quotas. Trade-off acceptable — un thread sans context cuda qui alloue est rare en steady state.
Total : +36 / -21 lignes. Deux fichiers touchés.
Le push et la review
git push -u origin fix/cumemcreate-uninit-dev
gh pr create --repo Project-HAMi/HAMi-core \
--base main \
--head aamsellem:fix/cumemcreate-uninit-dev \
--title "fix(cuda,allocator): initialize dev in 6 hooks reading uninitialised stack on cuCtxGetDevice failure" \
--body-file pr-body.md
PR live : Project-HAMi/HAMi-core#188.
Le body du PR explique :
- Quel est le pattern problématique
- Pourquoi il déclenche en pratique (multi-thread early-alloc, async scheduling, CUDA Graphs)
- Ce que chaque change fait dans chaque fichier
- Comment reproduire avec
repro.c - Le test end-to-end avec mon image Lucebox
- Les notes sur les choix de design (pourquoi skip le tracking au lieu de fall back sur device 0, pourquoi grouper le bounds-check fix avec le reste)
Avec Signed-off-by: aamsellem <620182+aamsellem@users.noreply.github.com> parce que la plupart des projets NVIDIA-adjacents demandent du DCO.
Et là, j’attends.
Pour Olares concrètement
Le truc, c’est qu’Olares ne pull pas master direct. Olares utilise beclab/hami:v2.6.14, image Docker Hub publiée le 18 mars 2026 — donc elle ne contient pas les changements upstream récents. Il faudra :
- Que le PR #188 soit merged sur HAMi-core master
- Que beclab rebuilde leur image
beclab/hami:v2.6.xfrom master - Qu’Olares update leur Helm chart pour pointer la nouvelle image
- Qu’au prochain Olares update, votre cluster pull la nouvelle libvgpu.so
Donc avant que ça arrive en prod, faudra patienter. Probablement 2-4 semaines.
En attendant, deux workarounds :
- Compiler libvgpu.so localement avec mon patch et la swap dans
/usr/local/vgpu/libvgpu.sosur le host Olares. Hot-swap, marche pour tous les pods GPU sans toucher l’image. Hack mais efficace. - Désactiver VMM dans ggml avec
-DGGML_CUDA_NO_VMM=ON. Mitigation partielle — fix le pathcuMemCreatemais pas les paths allocator. À utiliser si vous ne pouvez pas toucher le host.
Ce qu’on retient de cette saga (jusqu’ici)
- 6 builds Docker. Environ 12h de compile cumulées, plus 6h de debug, plus 4h de patch upstream.
- 3 fix non documentés découverts l’un après l’autre : libcuda.so.1 stub, rpath-link, NO_VMM (qui finalement ne sert qu’en mitigation partielle).
- 1 bug systémique upstream identifié et fixé en PR (#188).
- 0 token Lucebox généré pour le moment.
Oui zéro. Parce qu’à l’heure où j’écris ces lignes, le pod ne boote toujours pas — il faut soit que le PR upstream soit merged + arrive sur Olares, soit que je compile manuellement libvgpu sur le host.
Épisode 8 — On a enfin les chiffres Lucebox sur Blackwell consumer, à venir dès qu’on dépasse cette dernière marche. À 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.