7 draft
Voici les fondations, sans rédaction :
1. Le problème actuel
Points à couvrir, dans l'esprit de la table --jit-profile de blog/5 :
- Le tableau Iters/Sessions/RSS tel quel (30/500/5000 itérations) — c'est ton meilleur "hook" d'ouverture, comme le tableau de benchmark NanoGPT en intro de blog/5.
- La distinction device vs host :
IREE device allocator: peak 4KB, live 0KBvsphys_footprint (dirty): 7.85GB— ce contraste est frappant et mérite d'être mis en avant tôt, ça évite au lecteur de penser "fuite GPU classique". - Le mécanisme de ping-pong en 4 étapes (shape A compile → shape B invalide A,
compile → shape A invalide B, recompile...) — probablement le meilleur
candidat pour être présenté comme un
eprintlntrace réel, dans le style "code block observé" que tu utilises pour leIREE dispatch profilede blog/5. - Pourquoi ce n'est pas une bizarrerie CLEVR : le polymorphisme de shape est le cas normal en Sheaf, pas l'exception (à relier à ta philosophie "JAX-flavored Lisp" de blog/1).
2. Diagramme ASCII #1 — fonctionnement actuel
FunctionDef "sigmoid"
└── vmfb_session_idx: Option<usize> ← un seul slot, jamais un ensemble
Appel 1: sigmoid(f32[1,5])
vmfb_session_idx = None → compile → push #0 dans vmfb_sessions
vmfb_session_idx = Some(0)
Appel 2: sigmoid(f32[1,5,1])
check_shapes_match([1,5] vs [1,5,1]) → MISMATCH
vmfb_session_idx cleared → compile → push #1
vmfb_session_idx = Some(1) ← session #0 toujours dans le Vec, mais orpheline
Appel 3: sigmoid(f32[1,5])
check_shapes_match([1,5,1] vs [1,5]) → MISMATCH
vmfb_session_idx cleared → compile → push #2
vmfb_session_idx = Some(2) ← #0 et #1 orphelines
vmfb_sessions: [ #0(dead) #1(dead) #2(live) ... ] ← append-only, jamais purgé
└────────────┬────────────┘
~106MB × sessions mortes
Idée pour la légende : montrer que le graphe de vie "un seul index vivant"
contredit visuellement la croissance du Vec en dessous — c'est le cœur du bug
en une image.
3. Comment JAX fonctionne
- Structure :
xla::LRUCache<CallSignature, ...>parPjitFunction, capacité par défaut 4096. - Ce qui compose
CallSignature(jax_jit.h:141) : shape/dtype des args, structure pytree, sharding, device, config — donc deux shapes ≠ deux entrées coexistantes, pas une invalidation. - Le point clé pour ton lecteur : JAX traite le changement de shape comme un cache miss ordinaire, jamais comme une erreur qui invalide l'existant.
- Mentionner le disk cache secondaire (recompilation évitée entre process).
static_argnumscomme seul mécanisme voulu de thrashing, documenté comme un risque assumé par l'utilisateur — bon contrepoint pour montrer que même JAX peut thrasher, mais volontairement.
4. Comment PyTorch fonctionne
- Cache Dynamo : liste chaînée de
(guard_manager, code, next)surco_extra, walk + premier match gagne. - Les deux limites (
recompile_limit=8par région,accumulated_recompile_limit=256global) et surtout la dégradation explicite vers eager quand la limite est dépassée — c'est le contraste le plus utile avec Sheaf actuel, qui lui n'a aucun mécanisme de dégradation (il crashait avant le fix declear_failure, maintenant il boucle indéfiniment). - Automatic dynamic shapes : le mécanisme de "wobble" sur une dimension → une seule recompile avec dimension symbolique, N valeurs futures collapsent sur une entrée. Bon endroit pour introduire Option E et pourquoi elle reste hors scope (dépend de shapes dynamiques dans la pipeline de lowering, absentes de Sheaf aujourd'hui).
- Point structurel important pour la clarté de l'article : bien montrer que la stratégie multi-entrée (A) est le socle commun à JAX et PyTorch, et que les dynamic shapes sont un raffinement PyTorch par-dessus ce socle, pas une alternative.
5. Le choix retenu, et pourquoi on écarte les autres
- Retenu : D puis A. D (runtime/session IREE partagé, modules chargés par nom) règle le coût par artefact (~106MB observés) ; A (cache multi-shape borné par fonction, LRU) règle le nombre d'artefacts retenus. Le séquençage D-avant-A isole le refactor FFI risqué (lifetimes, Drop) et le rend testable indépendamment (RSS par module chargé avant/après).
- B (interpreter-only après seuil de thrash) écarté comme fix principal : contredit l'idiome central de Sheaf — le polymorphisme de shape est censé être gratuit, pas pénalisé. Reste pertinent comme filet de sécurité au-dessus de A, pas comme solution.
- C n'est pas une option distincte : c'est A avec une capacité fixée dès la conception plutôt qu'ajoutée après coup — bon endroit pour la leçon "borner dès le départ, pas après" (écho possible à la fuite elle-même : un slot unique non borné avait déjà été la cause du bug).
- E (dynamic shapes automatiques) écarté pour l'instant : suppose une
pipeline de lowering avec shapes symboliques, que Sheaf n'a pas encore
(
lowering/suppose du statique partout). Raffinement futur sur A, pas maintenant. - Point additionnel à ne pas oublier : les scalaires capturés (
temperature, etc.) doivent entrer dans la clé de cache aux côtés des shapes — sinon bug de correction silencieux, pas juste de perf. Bon endroit pour une note "ce qu'on a failli rater".
6. Diagramme ASCII #2 — architecture cible (D + A)
┌─────────────────────────────┐
│ IreeRuntime (unique, partagé) │
│ instance + device + allocator │
└───────────────┬─────────────┘
│ modules chargés par nom
┌────────────────────────┼────────────────────────┐
│ │ │
module "sigmoid module "sigmoid module "softmax
[1,5]" [1,5,1]" [B,S,V]"
(~qq KB, pas de (~qq KB) (~qq KB)
nouvelle instance)
JitCompiler
per_function_cache: HashMap<fn_name, LRU<ShapeSignature, ModuleHandle>>
capacité fixe (ex. 8), éviction FIFO/LRU
sigmoid → lookup ShapeSignature{shape, dtype, captured_scalars}
hit → dispatch direct
miss → compile, load module dans le runtime partagé, insert, evict si plein
Légende possible : contraster avec le diagramme #1 — même topologie logique (une fonction, plusieurs shapes possibles), mais le coût par entrée passe de "nouvelle instance de runtime" à "module chargé dans un runtime existant", et le nombre d'entrées passe de non-borné à borné.
Si tu veux, je peux aussi relire un brouillon une fois que tu l'auras rédigé — mais je te laisse la plume, comme demandé.