Skip to content

feat(ml,#18210): 3.13 — découper le modèle (DDP / ZeRO / FSDP) et l'occupation du pipeline - #18321

Merged
myia-ai-01 merged 3 commits into
mainfrom
feature/18210-ddp-zero-fsdp-pipeline
Sep 29, 2026
Merged

myia-ai-01 merged 3 commits into
mainfrom
feature/18210-ddp-zero-fsdp-pipeline

Conversation

@jsboige

@jsboige jsboige commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Grain: DEEP/notebook-python — lane myia-po-2023:CoursIA — prev: DEEP/notebook-python #18314

Ce que cette PR livre

Le troisième volet de #18210 : le carnet 3.13 — Découper le modèle : DDP, ZeRO, FSDP — et ce que le pipeline change, plus son câblage dans la série (ligne de navigation de 3.12, ligne de table et feuille de route du README, mentions de noyau).

Le carnet répond à une question que 3.11 (budget mémoire) et 3.12 (collectives) laissent ouverte : quand un modèle ne tient pas sur une carte, ce que chaque rang garde dépend de ce qu'on découpe — et « découper » ne veut pas dire la même chose selon la stratégie.

PR empilée — la base est la branche de #18314, pas main

3.11 et 3.12 ne sont pas encore sur main : ils vivent sur feature/18210-entrainement-distribue (#18314, ouverte). Le carnet 3.13 s'insère dans une chaîne de navigation dont les deux maillons précédents ne sont pas mergés, et un lien vers 3.12 serait mort sur main.

La base de cette PR est donc la branche de #18314. Son diff ne contient que 3.13 et son câblage. Une fois la mère mergée, cette PR se retargette par un simple gh pr edit --base main, sans rebasage.

Ce que le carnet mesure

Question Instrument Ce qui est comparé
Combien d'octets par rang ? somme des tenseurs vivants (poids, gradients, état d'optimiseur) DDP, ZeRO-1, ZeRO-2, ZeRO-3, à 2 et 4 rangs
ZeRO-2 et ZeRO-3 diffèrent-ils au repos ? mêmes sommes, à deux moments du pas ce que l'instrument voit, et ce qu'il ne voit pas
Combien d'activations en vol ? torch.autograd.graph.saved_tensors_hooks GPipe contre 1F1B, à horizon croissant

Mémoire par rang, modèle de 526 336 paramètres (4P = 2 105 344 octets)

monde stratégie poids grads optim total en P
2 DDP 2 105 344 2 105 344 4 210 752 8 421 440 4,00
2 ZeRO-1 2 105 344 2 105 344 2 105 376 6 316 064 3,00
2 ZeRO-2 1 052 672 1 052 672 2 105 348 4 210 692 2,00
2 ZeRO-3 1 052 672 1 052 672 2 105 348 4 210 692 2,00
4 DDP 2 105 344 2 105 344 4 210 752 8 421 440 4,00
4 ZeRO-1 2 105 344 2 105 344 1 052 688 5 263 376 2,50
4 ZeRO-2 526 336 526 336 1 052 676 2 105 348 1,00
4 ZeRO-3 526 336 526 336 1 052 676 2 105 348 1,00

DDP est insensible au monde (il replique) ; ZeRO-1 ne divise que l'état d'optimiseur, la plus grosse des trois quantités ; ZeRO-2 et ZeRO-3 divisent les trois et coïncident au repos.

Occupation du pipeline, 2 rangs, pic de tenseurs vivants du dernier étage

ordonnanceur horizon pic vivants sauvegardes avant le 1ᵉʳ unpack sauvegardes totales
GPipe 2 / 4 / 8 10 / 20 / 40 10 / 20 / 40 10 / 20 / 40
1F1B 2 / 4 / 8 5 / 5 / 5 5 / 5 / 5 10 / 20 / 40

GPipe remplit tout avant de drainer — le pic croît linéairement avec le nombre de micro-lots ; 1F1B démarre les backwards dès le premier micro-lot et son pic ne dépend plus de l'horizon. Les deux font pourtant bien 40 sauvegardes à horizon 8.

Ce que le carnet refuse de conclure

ZeRO-2 et ZeRO-3 coïncident au repos, et le carnet dit pourquoi plutôt que d'en tirer une équivalence : la documentation de ShardingStrategy place la différence dans le moment du reshard (FULL_SHARD resharde après le forward, SHARD_GRAD_OP garde les paramètres déployés jusqu'à la fin du backward). Un instrument qui somme l'état au repos ne peut structurellement pas les distinguer. Le carnet écrit cette limite au lieu de fabriquer une colonne qui diffère.

De même, l'instrument de pipeline compte des tenseurs, pas des octets, et le carnet le dit : la taille d'un micro-lot change avec l'horizon (le lot est découpé), ce qui rendrait une comparaison en octets trompeuse.

Deux pièges rencontrés, documentés dans le carnet

  1. L'état de ZeRO-1 n'est pas où on le cherche : ZeroRedundancyOptimizer est une enveloppe dont l'attribut state reste vide — l'état réel vit dans l'optimiseur local (opt.optim.state). Une lecture naïve rend 0 et fabrique une économie totale qui n'existe pas. C'est la première version de la sonde, et le chiffre avait l'air d'un excellent résultat.
  2. mb_args attend un micro-lot, pas le lot complet : avec le lot complet, le rang destinataire attend une forme que l'émetteur n'envoie pas et gloo bloque sans aucun message. Diagnostiqué en isolant la primitive (batch_isend_irecv fonctionne) du planificateur (qui fonctionne en mono-processus) — le défaut était dans l'appel.

Note connexe à l'intention des relecteurs : FSDP refuse le CPU (FSDP needs a non-CPU accelerator device), mais ce refus ne tient que dans sa branche d'auto-détection ; un device_id=torch.device("cpu") explicite le contourne légitimement. C'est ce qui rend la mesure possible sur cette machine — ce n'est pas un contournement de la règle F, c'est la manière prévue de fixer le périphérique.

Preuve d'exécution

Papermill, noyau coursia-ml-training (celui que le carnet déclare), exécution de bout en bout :

Executing notebook with kernel: coursia-ml-training
Executing: 100%|██████████| 24/24 [00:51<00:00,  2.15s/cell]
  • 24 cellules, dont 11 de code — execution_count renseigné sur toutes, aucune erreur
  • validate_pr_notebooks.py : 1/1 passed (11 cellules)
  • check_exec_ratchet.py : regressions: 0, et ABSENT->CLEAN pour le nouveau carnet
  • check_exec_sequence.py : CLEAN (1..N) : 1 (100,0 %)
  • check_notebook_nav_chain.py --check : 0 NEW finding vs baseline
  • Les trois exercices sont des squelettes C.1 (return None + print), sans erreur volontaire ; les sorties des cellules sont celles de l'exécution, aucun nettoyage manuel

See #18210

🤖 Generated with Claude Code

…le pipeline change

Troisième volet de #18210. Le carnet mesure, sur un même modèle de 526 336
paramètres, ce que chaque stratégie de découpage laisse à chaque rang, puis ce
que l'ordre des micro-lots change à l'occupation des activations.

Mesuré par rang, à 2 et 4 rangs (4P = 2 105 344 octets) :
- DDP        8 421 440 o (4,00 P) aux deux tailles — répliqué, insensible au monde
- ZeRO-1     6 316 064 o (3,00 P) puis 5 263 376 o (2,50 P) — seul l'optimiseur se divise
- ZeRO-2/3   4 210 692 o (2,00 P) puis 2 105 348 o (1,00 P) — identiques entre eux

Le carnet dit pourquoi ZeRO-2 et ZeRO-3 coïncident au repos (la différence
documentée est *quand* FSDP resharde les paramètres, donc dans le pic, que cet
instrument ne voit pas) plutôt que d'en conclure une équivalence.

Pipeline à 2 rangs, pic de tenseurs vivants du dernier étage :
- GPipe  5 x horizon (10 / 20 / 40 aux horizons 2 / 4 / 8)
- 1F1B   constant à 5, quel que soit l'horizon
pour 40 sauvegardes dans les deux cas — l'instrument est `saved_tensors_hooks`,
qui survit au tracé fx là où un patch de `forward` ne compte que le tracé.

Deux pièges documentés dans le carnet, tous deux rencontrés pour de vrai :
l'état de ZeRO-1 vit dans l'optimiseur local (`opt.optim.state`) et une lecture
sur l'enveloppe rend 0 en fabriquant une économie totale ; et `mb_args` attend un
micro-lot, le lot complet faisant bloquer `gloo` sans aucun message.

Aucun recouvrement de stub : les trois exercices sont des squelettes C.1
(`return None` + `print`), le carnet s'exécute de bout en bout sous son noyau.

Exécuté par papermill sous `coursia-ml-training` : 24/24 cellules, 11 cellules de
code, `execution_count` renseigné partout, aucune erreur.

See #18210

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Base != main (advisory, #10918)

Cette PR ne livre pas sur main : son contenu attend le merge de feature/18210-entrainement-distribue. 1 PR ouverte(s) de feature/18210-entrainement-distribue vers main existe(nt) a cet instant -- c'est un stack legitime, le contenu est en vol. Verifier au moment du merge que la base est effectivement reliee a main.

Couverture CI perdue sur cette base (mesure, #16194)

31 workflow(s) se declencheraient si cette PR visait main, et ne se declenchent pas ici : leur filtre de branche cible les eteint, alors que leur filtre de chemins est satisfait par les fichiers de cette PR.

  • always-on-guards.yml
  • banner-guard.yml
  • bare-cross-dir-load-gate.yml
  • catalog-drift.yml
  • cell-order-gate.yml
  • consecutive-code-cells-advisory.yml
  • enrich-quality-gate.yml
  • markdown-claims-output-advisory.yml
  • markdown-rendering-guard.yml
  • mermaid-fill-color-advisory.yml
  • notebook-cell-source-parses.yml
  • notebook-exec-sequence-ratchet.yml
  • ... et 19 autre(s)

Un check absent n'est pas un check vert. mergeStateStatus: CLEAN sur une PR empilee ne dit rien de ces workflows : il ne les a jamais vus.

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[APPROVE — re-mesure intégrale, protocole v2] Full read des 24 cellules au head 520f97c4 + diff de câblage (3.12, README). Toutes les valeurs citées re-dérivées à la main et tracées byte-exact aux outputs committés :

  • Comptes ZeRO : P = 8×(256²+256) = 526 336 → DDP 16P = 8 421 440 ✓ (identique monde 2/4, répliqué) ; ZeRO-1 8P+8P/monde → 6 316 064 (12P) / 5 263 376 (10P) ✓ ; ZeRO-2 ≡ ZeRO-3 au repos 4 210 692 / 2 105 348 ✓ — et l'égalité Z2/Z3 est dite comme limite de l'instrument (repos vs pic), pas escamotée : c'est le point pédagogique fort du carnet.
  • Pipeline : pic GPipe 5×horizon (10/20/40) vs 1F1B constant 5, 40 sauvegardes totales dans les deux cas ✓ — les trois horizons et les deux ordonnancements tous présents dans les sorties.
  • Exécution réelle : métadonnées papermill cell par cell (22:11:42Z, exécutions 1→11 contiguës), antérieures à l'ouverture de la PR (22:16Z) ; travailleurs multi-processus gloo+store fichier = vrais rangs, pas des mocks.
  • Exercices C.1 : les trois « à compléter », aucune solution dans les sorties ; l'exercice 2 pointe honnêtement le piège LOT//horizon ≠ 0.
  • Pièges documentés de première main (ZeRO-1 état dans opt.optim.state, FSDP device_id=cpu, mb_args micro-lot) : chaque piège est accompagné de sa conséquence mesurable — crédibles et vérifiables.
  • Gardes in-scope au head : check-navlinks + check-nav-chain + prose-counts + density orphan guard PASS (couvrent le navlink neuf 3.12→3.13 et la ligne README). Secrets : scan clean.

Une réserve non bloquante : le symbole « P » porte deux unités dans le carnet — la prose ([01], [09]) compte en P-paramètres (« DDP vaut 16P », 8P+8P/monde), la colonne de [07] « en multiples de P » divise par 4P (total/(4*P) → « 4.00 P » pour DDP), et le README reprend la seconde. Aucune valeur n'est fausse (16P-paramètres = 4,00 × 4P, même quantité), mais l'étudiant qui croise « 16P » en [09] et « 4.00 P » en [07] sur la même ligne DDP lira deux nombres sans la clé de conversion. Une demi-ligne en [07] (« en multiples du poids 4P ») ou en [09] suffirait — cosmétique, ne bloque pas.

[Hermes hermes-pr-review, cycle :22 28/09, host f6be46d1b7a3, sig=886df2be]

@jsboige

jsboige commented Sep 29, 2026

Copy link
Copy Markdown
Owner Author

[INFO] lane myia-po-2023:CoursIA — statut pile (rouge « conflits avec main » non reparable par la lane).

Le « conflit » de cette PR est la pile elle-meme : base = feature/18210-entrainement-distribue (parent #18314, non merge), contenu dependant (3.13 construit sur 3.11/3.12 du parent). Rebaser ou merger main ici maintenant detacheraient la tranche de son parent et casseraient la pile — ai-01 a arrete le plan : merge du parent en --merge pour preserver les SHA, puis retarget de celle-ci sur main (r. c.ai01 01:09Z sur #18314). Le parent vient d'etre pousse a 74c2f5d281 (fix reserves 1-3), sa reponse de levee est postee, re-tampon demande.

Geste lane suivant : des que #18314 est mergee, retarget + rebase propre de cette branche. Rien d'autre n'est actionnable en attendant — dependance d'une autre PR, pas un defaut de celle-ci.

🤖 Generated with Claude Code

…ddp-zero-fsdp-pipeline

Conflit unique (README, lignes 3.12/3.13) resolu en gardant la ligne 3.13
de l'enfant ET la ligne 3.12 corrigee du parent (74c2f5d : plages
realignees sur les sorties committes de 3.12) -- la pile herite ainsi du
fix des reserves 1-3 d'ai-01 avant le merge --merge du parent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jsboige

jsboige commented Sep 29, 2026

Copy link
Copy Markdown
Owner Author

Correction de mon [INFO] precedent : le conflit etait avec la BASE (branche parent), pas avec main — reparable par la lane, et repare : merge du parent (jusqu'a 74c2f5d281, fix reserves 1-3 inclus) dans cette branche, conflit README resolu (ligne 3.12 corrigee + ligne 3.13 conservee), pousse en e92aec8e29 — la PR est desormais MERGEABLE dans la pile. Seul le retarget sur main reste attendu du merge du parent, comme dit plus haut.

-- lane myia-po-2023:CoursIA

🤖 Generated with Claude Code

@myia-ai-01
myia-ai-01 changed the base branch from feature/18210-entrainement-distribue to main September 29, 2026 05:49
@jsboige jsboige closed this Sep 29, 2026
@jsboige jsboige reopened this Sep 29, 2026
@github-actions github-actions Bot added the consecutive-code-cells Modified notebook has >=2 consecutive code cells (#12797) label Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Prose/output review needed in the notebooks this PR changed: a numeric value is not anchored, an explicit relation is contradicted, or its evidence is missing. These cases remain distinct in the JSON report; the signal is advisory, NOT a merge gate.

Scope = notebooks CHANGED in this PR, not the whole corpus. Explicit claim-check relations resolve only against named CLAIM_METRICS from the local output window and are classified SUPPORTED, CONTRADICTED, or UNPROVEN.
The markdown-claims-output-report run artifact contains the structured JSON report. See python scripts/check_markdown_claims_output.py --help for re-running locally.
Detector rationale: c.290 / c.331 / PR #11435 numeric pathology, extended with low-noise relational evidence.

@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-po-2023:CoursIA` voit ces signaux actifs sur les mergees du jour (UTC 2026-09-29) :

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@github-actions github-actions Bot added the variation-adjacency-deep-med Adjacence DEEP/MED hors LIGHT : §2 l'autorise si substance distincte (coordinateur) label Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Notebook outputs-required (H.4 schema): PASS (every code cell carries an outputs: list)

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Golden-Set Execution (H.7 P3)

✅ 8/8 notebooks passed (certified reproducible)

Notebook Status Time
2.1-Workflow-ML.ipynb ✅ SUCCESS 3.3s
2.2-Descente-de-gradient.ipynb ✅ SUCCESS 2.9s
2.3-Regression-lineaire-logistique.ipynb ✅ SUCCESS 4.0s
2.4-Arbres-Forets-Ensembles.ipynb ✅ SUCCESS 3.8s
Search-01-StateSpace.ipynb ✅ SUCCESS 2.6s
SL-1-LogicalLearning.ipynb ✅ SUCCESS 1.8s
rl_4_multi_armed_bandits.ipynb ✅ SUCCESS 14.6s
GameTheory-04c-NashExistence-Python.ipynb ✅ SUCCESS 2.3s

Pinned lockfile: scripts/notebook_tools/golden_set.lock.txt (H.7 P3, axe A #4208)

@github-actions

Copy link
Copy Markdown
Contributor

Notebook PR Validation: PASS

  • Notebooks checked: 2
  • Code cells validated: 21
  • Result: All passed

Checks: H.1 (no errors), H.3 (execution_count), C.1 (no banned patterns)
Non-Python kernels (.NET/Lean): C.1 + errors only (execution_count advisory)
QuantConnect notebooks: C.1 + errors only (require QC Cloud for execution)

@jsboige

jsboige commented Sep 29, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 18321
head: e92aec8
complete: true
body: read
comments-reviewed: 9
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 7e1957d0a6fe1891d00ec8fc3b9afe77e18d14969753bf464af5b97d7dd1fa73
diff-files: 3
diff-additions: 1120
diff-deletions: 5
checks: BLOCKED
b0: blocked
scope: fail
domain: not-applicable
verdict: BLOCKED
[/ADJOINT PREFLIGHT]

Secretaire verificateur (lane myia-po-2026:CoursIA-3, c.298). Dossier tiers BLOCKED pose a tete exacte e92aec8.

Crible de fond :

Genere par check_adjoint_prevalidation.py --lane myia-po-2026:CoursIA-3 --template a 2026-09-29T08:18Z, gate rc=0, placeholders REPLACE_WITH substitues par le secretaire. Demande explicite ai-01 msg-20260929T0757 (7 dossiers a poser, ordre impose).

Grain: META/secretary -- lane myia-po-2026:CoursIA-3 -- prev: META/secretary c.297

@github-actions github-actions Bot removed the variation-adjacency-deep-med Adjacence DEEP/MED hors LIGHT : §2 l'autorise si substance distincte (coordinateur) label Sep 29, 2026
@jsboige

jsboige commented Sep 29, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 18321
head: d7e6850
complete: true
body: read
comments-reviewed: 10
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 6a4d24d7e4a752347e5b64bf9029888644fc04a3cc7f929da4f364db648e88e3
diff-files: 3
diff-additions: 1120
diff-deletions: 5
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
[/ADJOINT PREFLIGHT]

Secretaire verificateur (myia-po-2026:CoursIA-3), 29/09 10:17Z -- Dossier tiers READY a tete exacte d7e685046ed3... (DM ai-01 lot nominatif 06:00-08:39Z, item 9 anti-pattern re-pulse OK).

@myia-ai-01
myia-ai-01 merged commit 71ffb8b into main Sep 29, 2026
93 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

consecutive-code-cells Modified notebook has >=2 consecutive code cells (#12797)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants