Skip to content

feat(catalog,#17217): champ resource_cost -- cout d'execution mesure, cout de creation proxy - #17226

Merged
myia-ai-01 merged 2 commits into
mainfrom
feat/catalog-resource-cost
Sep 23, 2026
Merged

myia-ai-01 merged 2 commits into
mainfrom
feat/catalog-resource-cost

Conversation

@myia-ai-01

Copy link
Copy Markdown
Collaborator

Grain: MED/harness -- lane myia-ai-01:CoursIA -- paths: scripts/notebook_tools/generate_catalog.py, scripts/notebook_tools/tests/test_resource_cost.py, scripts/notebook_tools/tests/test_generate_catalog.py

Ce que le champ ajoute

Un cinquième axe au catalogue : resource_cost, ce qu'un notebook coûte à exécuter et ce qu'il a coûté à produire. Demande user, explicitement « pas besoin d'être hyper précis pour l'instant, ça pourra s'affiner » — d'où un champ qui rend ses mesures brutes à côté de sa classe, pour qu'un affinage ultérieur ne reparte pas de zéro.

// ICT-25-InoculationRL.ipynb, valeurs reelles
"resource_cost": {
  "schema": 1,
  "execution": {"class": "VERY_HEAVY", "wall_seconds": 17761.9, "cells_timed": 18,
                "cells_code": 20, "coverage": "PARTIAL", "external": ["api", "gpu"]},
  "creation":  {"class": "HEAVY", "history": "COMPLETE", "revisions": 31,
                "authors": 2, "first_commit": "2026-07-16", "span_days": 56}
}

L'exécution est mesurée, pas estimée

86,7 % du corpus porte des horodatages metadata.execution écrits par nbclient/papermill, 6,7 % partiellement. Le coût d'exécution est donc une somme de durées réelles, pas l'heuristique « 2 min par cellule » de duree_estimee (qui reste, elle sert à autre chose : annoncer une durée à l'étudiant).

Distribution mesurée sur les 1351 notebooks du corpus :

execution.class creation.class
LIGHT (< 60 s) 73,7 % LIGHT (≤ 2 révisions) 30,6 %
MODERATE (< 10 min) 16,3 % MODERATE (≤ 11) 40,9 %
HEAVY (< 1 h) 3,0 % HEAVY (≤ 34) 21,9 %
VERY_HEAVY 0,3 % VERY_HEAVY (> 34) 6,5 %
UNKNOWN 6,7 % UNKNOWN 0,1 %

Coût d'exécution cumulé du corpus : 38,8 h sur 1261 notebooks chronométrés. Les quatre plus lourds : ICT-25-InoculationRL (4,9 h), ICT-40a-TriangulationCausale (1,3 h), Sudoku-16-NeuralNetwork-Python (1,2 h), FLUX-1-Advanced-Generation (1,2 h).

À comparer aux trois autres axes déclaratifs, mono-valués à ~90 % (#17217) : celui-ci discrimine réellement, notamment côté création.

La création est un proxy, et c'est écrit dans le code

revisions compte les commits touchant le notebook, accumulés dans la passe git log que build_git_metadata fait déjà — elle parcourt tout l'historique pour n'en retenir que le commit le plus récent, donc compter est gratuit ; un second parcours ne l'est pas.

Ce que le proxy ne voit pas, et qui est dit dans le commentaire de section plutôt que maquillé : le squash-merge écrase chaque PR en un commit, donc revisions compte des PRs, pas des cycles ; ni les cycles d'agent sans commit, ni les tokens brûlés, ni le temps humain n'y figurent. Les auteurs distincts ne discriminent presque rien (médiane 1, max 4) : ils sont rendus, ils ne classent pas.

Le défaut que cette PR a failli publier

Mes premiers seuils étaient calibrés sur un instrument aveugle. Le clone d'ai-01 est shallow, coupé au 2026-08-25. La distribution que j'y ai mesurée — médiane 3 révisions, maximum 13 — ne décrit pas la vie des notebooks, elle décrit 27 jours. Après approfondissement, sur 2309 notebooks remontant à 2024 : q1 2, médiane 4, q3 11, p90 27, p95 35, max 64. Les seuils initiaux auraient classé presque tout le corpus en HEAVY, c'est-à-dire reproduit exactement le défaut mono-valué que #17217 reproche aux autres axes.

Deux conséquences dans le diff :

  1. Les seuils sont recalibrés sur la distribution complète, et les chiffres qui les fondent sont dans le commentaire de section — pour qu'un prochain passage les conteste avec des mesures, pas avec un avis.
  2. Le champ sait quand il ne peut pas répondre. Un notebook dont le plus vieux commit est une frontière de greffe rend class: UNKNOWN, history: TRUNCATED, et sa first_commit est tue : sur un clone coupé c'est la date de coupe, la publier fabriquerait l'âge qu'on prétend mesurer. Les comptes, eux, restent rendus — ils sont vrais pour la fenêtre.

git rev-parse --is-shallow-repository ne convient pas pour ça, et c'est mesuré : sur ce dépôt une fois approfondi il rend encore true à cause de six greffes résiduelles dont aucune n'est dans l'historique de MyIA.AI.Notebooks — dont le plus vieux commit remonte à 2024 et porte un parent. S'y fier rendrait UNKNOWN partout précisément là où le champ fonctionne. Le prédicat retenu est donc par notebook : son plus vieux commit est-il dans .git/shallow ?

Fail-closed, partout

  • Pas d'horodatage → UNKNOWN, jamais LIGHT. Un notebook qu'on n'a pas pu chronométrer n'est pas un notebook rapide.
  • Pas d'historique → UNKNOWN/ABSENT. history_truncated vaut True par défaut : un appelant qui oublie de le poser obtient UNKNOWN, pas une classe inventée.
  • shallow_boundaries() rend None si la question n'a pas pu être posée, et None est traité comme coupé.
  • Durée négative (horloge qui recule) ou > 10 h par cellule : écartée du total et décomptée de la couverture, jamais additionnée.

Une ressource externe est nommée, elle ne corrige pas la classe

Le temps de paroi d'un appel d'API est de la latence réseau, pas le calcul brûlé à l'autre bout. Un notebook requires_api classé LIGHT est léger pour cette machine, pas pour le monde. external: ["api","gpu"] est donc rendu à côté de la classe sans la corriger : corriger la classe fabriquerait un chiffre que rien ne mesure. La limite est écrite dans le code et gardée par un test.

Validation

$ python -m pytest scripts/notebook_tools/tests/ -q -k "catalog or git or resource"
531 passed

$ python -m pytest scripts/notebook_tools/tests/ -q        # suite complete
5972 passed, 3 skipped, 3 xfailed in 1699.96s (0:28:19)

13 tests neufs. Les quatre propriétés qu'ils épinglent sont rangées par dégât : (1) une absence de mesure ne devient jamais « léger » ; (2) un horizon de clone n'est pas une date de création ; (3) une durée aberrante est écartée ; (4) les seuils discriminent — chacune des huit bornes est franchie dans les deux sens.

test_generate_catalog.py est touché parce que le format de git log gagne %H : son contrôle positif portait l'ancien format et a échoué, ce qui est exactement son rôle. Le fixture est aligné et les assertions étendues aux trois champs neufs.

Aucun notebook touché. COURSE_CATALOG.generated.* non touché — byte-identique à main.

À savoir avant de merger

  • Le catalogue grossit d'environ 34 % : +299 o par entrée, soit ~1077 Ko → ~1439 Ko sur 1240 entrées. C'est le prix d'un champ qui rend ses mesures brutes ; le réduire à la seule classe le rendrait inaffinable.
  • Le champ n'est pas encore surfacé dans COURSE_CATALOG.generated.md ni dans les README de série. C'est un choix de présentation à part entière (quelle colonne céder), pas un oubli — geste séparé.
  • Les seuils sont une première calibration, pas un acquis. Ils sont des constantes nommées en tête de section précisément pour se contester.

See #17217

🤖 Generated with Claude Code

… cout de creation proxy

Cinquieme axe du catalogue : ce qu'un notebook coute a EXECUTER et ce qu'il a
coute a PRODUIRE. Demande user, explicitement « pas besoin d'etre hyper precis
pour l'instant, ca pourra s'affiner » -- d'ou un champ qui rend ses mesures
brutes a cote de sa classe, pour qu'un affinage ne reparte pas de zero.

L'execution est MESUREE : 86,7 % du corpus porte des horodatages
`metadata.execution` (nbclient/papermill), 6,7 % partiellement. Le cout est une
somme de durees reelles, pas l'heuristique de `duree_estimee` (qui reste, elle
sert a annoncer une duree a l'etudiant). Cout cumule du corpus : 38,8 h sur
1261 notebooks chronometres.

La creation est un PROXY, declare comme tel dans le code : `revisions` compte
les commits, accumules dans la passe `git log` que `build_git_metadata` fait
deja. Le squash-merge ecrase chaque PR en un commit, donc il compte des PRs et
pas des cycles ; ni les cycles d'agent sans commit, ni les tokens, ni le temps
humain n'y figurent.

Contrairement aux trois autres axes declaratifs (mono-values a ~90 %, cf
#17217), celui-ci discrimine : execution LIGHT 73,7 / MODERATE 16,3 / HEAVY 3,0
/ VERY_HEAVY 0,3 / UNKNOWN 6,7 ; creation LIGHT 30,6 / MODERATE 40,9 /
HEAVY 21,9 / VERY_HEAVY 6,5.

Le defaut que ce commit a failli publier : mes premiers seuils etaient calibres
sur un clone SHALLOW coupe au 2026-08-25. La distribution mesuree la (mediane 3
revisions, max 13) ne decrit pas la vie des notebooks, elle decrit 27 jours.
Sur l'historique complet -- 2309 notebooks remontant a 2024 -- c'est q1 2,
mediane 4, q3 11, p90 27, p95 35, max 64. Les seuils initiaux auraient classe
presque tout le corpus en HEAVY, soit exactement le defaut mono-value reproche
aux autres axes. Ils sont recalibres, et les chiffres qui les fondent sont dans
le commentaire de section pour se contester avec des mesures.

Fail-closed partout :
- pas d'horodatage -> UNKNOWN, JAMAIS LIGHT (un notebook non chronometre n'est
  pas un notebook rapide) ;
- plus vieux commit sur une frontiere de greffe -> UNKNOWN/TRUNCATED, et
  `first_commit` TUE : sur un clone coupe c'est la date de coupe, la publier
  fabriquerait l'age qu'on pretend mesurer. Les comptes restent rendus, vrais
  pour la fenetre ;
- `history_truncated` vaut True par defaut -- un appelant qui l'oublie obtient
  UNKNOWN, pas une classe inventee ;
- `shallow_boundaries()` rend None si la question n'a pas pu etre posee, et
  None est traite comme coupe ;
- duree negative ou > 10 h par cellule : ecartee du total ET decomptee de la
  couverture, jamais additionnee.

`git rev-parse --is-shallow-repository` ne convient pas pour ce verdict, et
c'est mesure : sur ce depot une fois approfondi il rend encore `true` a cause
de six greffes residuelles dont AUCUNE n'est dans l'historique de
MyIA.AI.Notebooks. S'y fier rendrait UNKNOWN partout la ou le champ marche. Le
predicat est donc par notebook : son plus vieux commit est-il une greffe ?

Une ressource externe est nommee sans corriger la classe : le temps de paroi
d'un appel d'API est de la latence, pas le calcul brule a l'autre bout.
Corriger la classe fabriquerait un chiffre que rien ne mesure.

`test_generate_catalog.py` est touche parce que le format de `git log` gagne
%H : son controle positif portait l'ancien format et a echoue -- c'est son
role. Fixture aligne, assertions etendues aux trois champs neufs.

Cout : le catalogue grossit d'environ 34 % (+299 o par entree). C'est le prix
d'un champ qui rend ses mesures brutes ; le reduire a la seule classe le
rendrait inaffinable.

See #17217

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jsboige

jsboige commented Sep 21, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 17226
head: a57bcf7
complete: true
body: read
comments-reviewed: 0
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 2d396e90115f59e785ed19c5925f2eff913f6bf964d4f86c4231a745feba5ad1
diff-files: 3
diff-additions: 415
diff-deletions: 8
checks: blocked
b0: clear
scope: pass
domain: pass
verdict: BLOCKED
[/ADJOINT PREFLIGHT]

Verdict BLOCKED, cause nommee. 17 check-runs dedupliques par (started_at, id) au head a57bcf7 : 9 pending, 1 rouge. Le rouge est PR gate (quota d'installation GitHub, echec de flotte). Les 9 pending -- dont Exec-sequence ratchet (base vs PR), Scripts Tests (CPU), Gitleaks secret scanner -- interdisent un claim vert. mergeable = true, b0 rc=0. Re-emission requise apres stabilisation des pending.

@github-actions github-actions Bot added variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1 variation-tag-prev-absent Tag Grain sans 'prev: <TIER>/<GENRE> #<PR>' (adjacence G-VAR-3 inevaluable) labels Sep 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #17226 (feat(catalog,#17217): champ resource_cost -- cout d'execution mesure, cout de creation proxy) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

@jsboige

jsboige commented Sep 21, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 17226
head: a57bcf7
complete: true
body: read
comments-reviewed: 3
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 8d84fff1dc953f917f740057a23fc0cc8b4fb98356d401e8d0d8d003705be7b4
diff-files: 3
diff-additions: 415
diff-deletions: 8
checks: blocked
b0: clear
scope: pass
domain: pass
verdict: BLOCKED
[/ADJOINT PREFLIGHT]

Verdict BLOCKED, causes nommees : 1 check pending residuel + 1 rouge PR gate (quota d'installation GitHub, echec de flotte). 18 check-runs dedupliques par (started_at, id) au head a57bcf7. mergeable=true, b0 rc=0. Re-emission requise apres stabilisation du pending et disparition du rouge.

@jsboige

jsboige commented Sep 23, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 17226
head: e711acd
complete: true
body: read
comments-reviewed: 4
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: bdfb4d75362fc467f7ea266aae5cbe9fee9ed23f2bc3dfa8af25647be1739348
diff-files: 3
diff-additions: 415
diff-deletions: 8
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
[/ADJOINT PREFLIGHT]

@jsboige

jsboige commented Sep 23, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 17226
head: e711acd
complete: true
body: read
comments-reviewed: 5
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 6fe93475285a6ca04b57cbd25daaa291018344bff9796b3f944c8e55132e19c9
diff-files: 3
diff-additions: 415
diff-deletions: 8
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
[/ADJOINT PREFLIGHT]

@jsboige

jsboige commented Sep 23, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 17226
head: e711acd
complete: true
body: read
comments-reviewed: 6
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 3b3459d543280d84ee05a442c79773a4ae0083354be288326930b33d8587d427
diff-files: 3
diff-additions: 415
diff-deletions: 8
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
[/ADJOINT PREFLIGHT]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1 variation-tag-prev-absent Tag Grain sans 'prev: <TIER>/<GENRE> #<PR>' (adjacence G-VAR-3 inevaluable)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants