# Journal des décisions (ADR léger) Chaque décision structurante est enregistrée ici : **contexte → décision → conséquences**. --- ## ADR-000 — Initialisation du projet - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : construire une IA multimodale from scratch, sans aucun poids pré-entraîné. - **Décision** : démarrer par une **Phase 0** de fondations (structure, config, docs, tests TDD rouges) avant tout code de modèle. - **Conséquences** : socle stable, vérifiable, avant d'écrire la moindre couche de neurones. --- ## ADR-001 — Interdiction stricte des modèles pré-entraînés - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : le cahier des charges interdit `transformers.AutoModel`, CLIP, BLIP, LLaVA, Whisper, Wav2Vec, ViT pré-entraîné, Qwen, Llama, DeepSeek, Mistral, GPT. - **Décision** : n'autoriser que PyTorch, numpy, torchaudio (I/O), torchvision (transforms), tokenizers (entraînement de **notre** tokenizer), datasets, huggingface_hub, FastAPI, Gradio, pytest, ruff, Docker. - **Conséquences** : `transformers` n'est **pas** une dépendance. Un test automatique (Phase 0) échoue si un import interdit apparaît dans `src/`. --- ## ADR-002 — Positional encoding : RoPE pour le texte, appris pour l'image/audio - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : le texte est une séquence de longueur variable (longue portée, besoin d'extrapolation) ; l'image et l'audio ont une structure de grille/trames de taille fixe. - **Décision** : **RoPE** (rotary) pour l'encodeur/décodeur texte ; **embedding positionnel appris** pour le ViT et l'encodeur audio. - **Conséquences** : meilleure extrapolation en longueur pour le texte ; simplicité et suffisance pour des grilles de taille fixe. Détails en `architecture.md`. --- ## ADR-003 — Profile 50M honnête - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : le cahier des charges demande « ~50M params (n_layer=6, n_head=6, n_embd=384, ctx=512) », mais cette combinaison avec vocab 32k donne ~22.9 M (lié) / ~35.2 M (non lié). - **Décision** : conserver le profil demandé **et** ajouter `text_model_50m` (`n_embd=512, n_head=8, n_layer=6`, embeddings non liés ≈ **51.6 M**). Documenter l'écart. - **Conséquences** : transparence ; l'utilisateur choisit le profil. Pas de fausse déclaration de taille. --- ## ADR-004 — Aucun secret en dur ; HF_TOKEN par variable d'environnement - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : publication sur un dépôt Hugging Face privé/public. - **Décision** : le token est lu depuis `HF_TOKEN` (via `.env` gitignoré). Jamais dans le code, la config, les notebooks ou les commits. - **Conséquences** : `.env` et `.env.example` séparés ; `.gitignore` bloque `.env`. --- ## ADR-005 — Tests unitaires sur CPU, entraînement sur Colab - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : machine de développement = CPU seul (4 cœurs, 15 Go, pas de GPU). - **Décision** : toutes les tailles de test dans `configs/model.yaml > test` sont minuscules ; l'entraînement réel se fait via un notebook Colab par phase. - **Conséquences** : `pytest` reste rapide ; aucune promesse d'entraînement local. --- ## ADR-006 — Une phase à la fois, validation explicite requise - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : règle 1 du cahier des charges. - **Décision** : ne jamais passer à la phase suivante sans tests verts + commit/push Hub + validation explicite (« phase X validée »). - **Conséquences** : progression lente mais contrôlée et vérifiable. --- ## ADR-007 — Licence Apache-2.0 - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : la Phase 0 avait posé un placeholder MIT, explicitement à remplacer. - **Décision** : adopter **Apache-2.0**. Le texte intégral est dans `LICENSE`, la model card (`README.md`) porte `license: apache-2.0`, et `pyproject.toml` déclare `license = { text = "Apache-2.0" }`. - **Conséquences** : - Licence permissive avec **clause de brevet explicite** (section 3), avantageuse pour un projet modèle/IA réutilisé par des tiers. - Obligation de **conserver les notices** et de signaler les fichiers modifiés en cas de redistribution. - Plus verbeuse que MIT ; aucune contrainte copyleft. ## ADR-008 — Nom du modèle : Cortex-0 - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : le projet était désigné « OpenDevin Multimodal ». L'auteur fixe l'identité publique du modèle. - **Décision** : nommer le **modèle** `Cortex-0` (README, `configs/model.yaml > project.name`, model card, package `pyproject.toml` → `cortex-0`). Le **dépôt Hugging Face conserve** `Frankenstein-Labs/opendevin-multimodal` jusqu'à la Phase 12 (renommage prévu alors). - **Conséquences** : identité claire du modèle ; une variable `PROJECT_NAME=Cortex-0` et un futur renommage de dépôt clairement planifié. Aucune ambiguïté entre nom du modèle et nom du dépôt. ## ADR-009 — Configuration finale verrouillée - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : l'auteur a figé les paramètres structurants à l'issue de la Phase 0. - **Décision** : | Paramètre | Valeur | |---|---| | Licence | Apache-2.0 | | Nom du modèle | Cortex-0 | | Organisation HF | Frankenstein-Labs | | Dépôt cible | Frankenstein-Labs/opendevin-multimodal | | MoE (Phase 10) | **20 experts, top-2** | | Langue principale | français | | Matériel | CPU (dev/tests) + Colab (entraînement) | - **Conséquences** : `configs/model.yaml > moe.n_experts` passe de 8 à 20 ; le coût mémoire du MoE augmente d'autant (tous les experts résident en mémoire), ce qui sera réévalué lors de la Phase 10. --- ## ADR-010 — Tokenizer : BPE ByteLevel, corpus FR plafonné, 19 tokens spéciaux - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : la Phase 1 exige un BPE de 32 000 tokens sur corpus français public, 100 % from scratch, capable de gérer accents, emojis et caractères inconnus. - **Décision** : - **BPE** avec `pre_tokenizer=ByteLevel(add_prefix_space=False)`, `byte_fallback=True` (sur le modèle), `min_frequency=2`. - **`initial_alphabet=ByteLevel.alphabet()`** : indispensable pour un round-trip exact. - **19 tokens spéciaux** réservés en premier (4 base + 6 raisonnement + 9 modalités), ids 0..18 fixes → stables entre runs. - **Corpus plafonné à ~150–200 Mo** de `wikimedia/wikipedia` (config `20231101.fr`), en **streaming** (pas de téléchargement des ~6 Go). - Corpus « propre » : pas d'en-tête de licence ni de boilerplate. - **Conséquences** : - Round-trip exact pour toute entrée, y compris hors corpus. - `vocab_size` traité comme borne supérieure ; échec explicite si le corpus est trop pauvre (pas de résultat trompeur). - Mesuré : 32 000 tokens en 21,8 s en local (CPU) sur 150 Mo. - Recherche d'un vocabulaire « 32000 exact » : atteint, donc **aucune réduction de scope nécessaire**. --- ## ADR-011 — Transformer texte : RoPE, pre-norm, attention causale à la main - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 2 — coder un transformer decoder-only from scratch, sans `transformers` ni `nn.MultiheadAttention`. - **Décision** : - **RoPE** (rotation des paires de dimensions de Q/K) plutôt qu'un embedding positionnel absolu appris : position **relative** dans `Q·K`, zéro paramètre, meilleure extrapolation, réutilisable pour toutes les modalités. - **Pre-norm** (`x = x + F(LN(x))`) : gradient non multiplié par la norme sur le chemin résiduel → entraînement plus stable. - **Masque causal** triangulaire à `-inf` avant softmax. - **FFN GELU** à deux couches, `ffn_mult=4`. - **`lm_head`** optionnellement lié aux embeddings. - Génération (`temperature`, `top-k`, `top-p`, glouton) écrite à la main, sans `model.generate`. - **Conséquences** : - Causalité et invariance relative **prouvées par tests**, pas supposées. - Le profil demandé (`n_embd=384, n_layer=6`) fait **22.9 M**, pas 50 M : écart documenté, le profil `text_model_50m` (512 dim, non lié) fait bien **51.6 M**. - Mesuré en local : overfit d'un mini-corpus FR 10.45 → **0.011** en 68 s. --- ## ADR-012 — Entraînement texte : données memmap uint16, AdamW sélectif, cosine manuel - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 3 — entraîner le transformer texte sur corpus public, en pur PyTorch (pas de `Trainer`). - **Décision** : - Tokenisation en **tableau `uint16`** (`train.bin`/`val.bin`) + `np.memmap` : 2 octets/token, zéro copie RAM, découpe val **séquentielle** (pas de fuite). - **AdamW** avec weight decay appliqué **seulement** aux tenseurs `dim >= 2`. - **Warmup linéaire + cosinus** écrits à la main. - **Gradient clipping** 1.0, **autocast** bf16/fp16 sur GPU. - Checkpoints `last.pt`/`best.pt`, logs JSONL. - **Conséquences** : - L'entraînement réel peut tourner sur CPU (mesuré) comme sur Colab GPU. - La cible ppl < 30 est **budgétée**, pas garantie sur CPU ; le notebook Colab est le chemin nominal. Aucun chiffre annoncé sans mesure. --- ## ADR-013 — Encodeur image ViT : patchs, position apprise, attention bidirectionnelle - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 4 — encodeur image from scratch, aligné sur l'espace texte. - **Décision** : - **Patch embedding** par convolution noyau/pas = patch_size. - **Positional embedding appris** (grille fixe) plutôt que RoPE 2D. - **Attention bidirectionnelle** (`causal=False`) : vital, sinon le CLS ne voit pas les patchs (bug réellement rencontré et corrigé). - Token **CLS** pour le résumé global (pool `cls`), alternative `mean`. - **Projecteur MLP** `LayerNorm -> Linear -> GELU -> Linear` vers `text_dim`. - **Générateur d'images jouets maison** (9 classes forme x couleur) pour tester sans données externes. - **Conséquences** : - Le drapeau `causal` de l'attention est partagé texte (causal) / encodeurs (non causal). - Overfit vérifié : classification acc > 0.9, reconstruction MSE < 0.05 (CPU). --- ## ADR-014 — CLIP maison : lots à légendes uniques, masque de padding, clipping - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 5 — alignement image-texte from scratch. - **Décision** : - Loss contrastive **symétrique** écrite à la main (InfoNCE lignes+colonnes). - **Masque d'attention** jusqu'au pooling texte (ignorer les PAD). - Lots construits avec des **légendes uniques** (`_build_batches`) : sans cela, l'appariement 1-1 est violé et la perte collapse. - `logit_scale` apprise (température), embeddings **normalisés L2**. - **Gradient clipping 1.0** ; lr modéré (2e-3) — un lr élevé collapse. - Métrique de retrieval **au niveau des classes**. - **Conséquences** : - Convergence réelle mesurée : retrieval 0.81 (hasard 0.11), loss 2.20 -> 0.15. - Deux bugs silencieux identifiés et corrigés avec tests de régression. --- ## ADR-015 — VLM : fusion par embeddings, décalage des labels, métrique de grounding - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 6 — VLM génératif image+texte from scratch. - **Décision** : - Fusion par **injection d'embeddings** (`inputs_embeds`) : patchs projetés insérés entre `<|image_start|>` et `<|image_end|>`. - **Labels décalés** (`labels[t]=ids[t+1]`) ; perte masquée par `ignore_index=-100`. - Métrique principale = **exactitude teacher-forced** (grounding) ; la génération libre est rapportée séparément comme métrique plus difficile. - **Conséquences** : - Bug silencieux évité : labels non décalés → loss 0.0006 mais génération cassée. - Mesuré CPU : légende 0.85 / forme 0.80 / couleur 1.00 (teacher-forced), génération libre 0.35 (biais d'exposition assumé). --- ## ADR-016 — Audio : mel maison, encodeur bidirectionnel, données synthétiques - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 7 — encodeur audio from scratch. - **Décision** : - Mel-spectrogramme **écrit à la main** (torch.fft + filtres mel) car torchaudio est absent de l'environnement (plus « from scratch », zéro dépendance audio). - Encodeur : patch embedding 2D (fréquence × temps) + blocs **bidirectionnels**. - Données : **voyelles synthétiques** (formants F0/F1/F2, 10 classes), faute d'accès audio dans l'environnement de dev. - **Conséquences** : - Transcription parfaite sur le jeu jouet (acc 1.00). Parole réelle non testée : réduction de scope assumée et documentée. --- ## ADR-017 — Modèle complet : un décodeur pour trois modalités - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 8 — texte + image + audio dans un seul modèle. - **Décision** : - Un unique décodeur texte ; image et audio projetés puis injectés comme tokens spéciaux dans la même séquence (fusion précoce). - Dans un lot multi-tâches, n'encoder que les lignes contenant la modalité. - Métrique principale : exactitude teacher-forced par tâche ; génération libre rapportée séparément. - **Conséquences** : - Mesuré CPU : image 0.92 / audio 1.00 / mixte 0.93 (teacher-forced) ; génération libre mixte 0.17 (limite assumée, modèle de taille jouet). --- ## ADR-018 — Vidéo : ViT partagé sur frames + transformer spatio-temporel - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 9 — vidéo (frames + audio synchronisés). - **Décision** : - Vidéos jouets = forme colorée qui se déplace (direction = tâche temporelle). - `VideoEncoder` = ViT partagé par frame + embedding temporel appris + transformer spatio-temporel bidirectionnel sur tous les `n_frames × n_patches` tokens. - `n_frames` petit (4) à cause du coût quadratique de l'attention. - **Conséquences** : - Grounding mesuré CPU : vidéo 0.78, vidéo+audio 0.85 (18 classes). - Génération libre 0.0 : limite assumée et documentée. --- ## ADR-019 — MoE 20 experts top-2 dans le FFN - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : Phase 10 — capacité accrue à coût de calcul borné. - **Décision** : - `MoEFFN` remplace le FFN dense ; config imposée : 20 experts, top-2. - Routeur linéaire top-k + renormalisation des portes + bruit d'exploration. - Loss auxiliaire Switch-Transformer (coef 0.01) pour équilibrer le routage. - Calcul épars : chaque expert ne traite que ses tokens. - **Conséquences** : - Mesuré CPU : perplexité val 1.005, entropie de charge 0.9999 (routage équilibré). - 14.86M paramètres totaux / 5.42M actifs par token (~2.7x). - Tous les experts résidents en mémoire ; routage à vectoriser à grande échelle. --- ## ADR-020 — Mémoire : RAG (mode A) + apprentissage permanent (mode B) - **Date** : 2026-10-10 - **Statut** : accepté (mode B : expérimental) - **Contexte** : Phase 11 — « apprendre des cours ». - **Décision** : - Mode A (RAG) : embeddings maison par feature hashing IDF + index numpy cosinus ; réponse extractive verbatim avec sources (vidéo = transcription + légendes frames). - Mode B (apprentissage permanent) : quelques pas de gradient à l'ingestion, avec tampon de rejeu. Marqué EXPÉRIMENTAL ET INSTABLE, tests qui prouvent l'oubli. - **Conséquences** : - Mode A fiable, sans hallucination ; mode B honnête mais instable, non garanti. --- ## ADR-008 — Nom du modèle « Cortex-0 » vs nom du dépôt (renommage différé) - **Date** : 2026-10-10 - **Statut** : accepté - **Contexte** : le modèle s'appelle **Cortex-0**, mais le dépôt Hub reste `Frankenstein-Labs/opendevin-multimodal` pour ne pas casser les liens déjà publiés. - **Décision** : ne **pas** renommer le dépôt à ce stade ; documenter l'écart (nom de modèle « Cortex-0 », dépôt « opendevin-multimodal ») dans le README. Un renommage éventuel (redirection Hub) est laissé à une décision ultérieure. - **Conséquences** : README et carte de modèle portent « Cortex-0 » ; le remote reste stable. --- ## ADR-021 — Phase 12 : moteur unique, API FastAPI, démo Gradio, Docker - **Date** : 2026-10-10 - **Statut** : accepté - **Décision** : - Un `CortexEngine` unique (texte/image/audio/vidéo) partagé par l'API et la démo. - API FastAPI testée par `TestClient` ; handlers Gradio testés sans serveur. - Décodage d'entrées sans dépendance lourde (numpy/Pillow/`wave`), vidéo en `(T,H,W,3)`. - `CORTEX_CHECKPOINT`/`HF_TOKEN` au runtime ; aucun poids pré-entraîné dans l'image. - **Conséquences** : 14 tests Phase 12 ; publication du checkpoint + carte avec licence Apache-2.0.