ia : détection de dérive du modèle de prévision (#117) #238
Labels
No labels
Compat/Breaking
EC01
EC02
EC03
EC04
EC05
EC06
Kind/BDD
Kind/Back
Kind/Bug
Kind/CICD
Kind/Cloud
Kind/Contenu
Kind/Data
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Front
Kind/IA
Kind/Infra
Kind/Monitoring
Kind/Security
Kind/Testing
Portée/Post-jury
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
ops/alerte
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
g2/enervision!238
Loading…
Reference in a new issue
No description provided.
Delete branch "florian/117-derive-modele"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Un modèle ne tombe pas en panne, il se démode. La passe horaire compare
désormais les entrées et l'erreur du modèle promu à une référence FIGÉE À SA
PROMOTION, lève un signal au-delà de seuils déclarés, et publie son verdict
pour Prometheus.
Ce que le ticket demandait, et où c'est :
inference/derive.py— la règle, en Python pur. Trois écarts : déplacementde la moyenne des entrées en écarts-types de la référence, rapport des
dispersions (rendu dans les deux sens, un capteur bloqué fait chuter la
dispersion autant qu'une agitation la fait monter), rapport des erreurs.
Verdict à trois valeurs et non deux : « surveillance » à mi-chemin de la
borne évite qu'une mesure oscillante allume et éteigne l'alerte.
model/entrainement.py— journalise la référence (moyenne, dispersion,effectif des entrées apprises) dans l'exécution MLflow. Elle est donc
adressée par (modèle, version) et ne peut pas survivre au modèle.
inference/reference_promue.py— la relit par le protocoleRegistredu#36. Une version sans référence rend None et le dit : la prévision continue
d'être servie, la dérive n'est pas mesurée.
inference/realise.py— remplitpublic.prevision.valeur_reference_kw, lacolonne que la migration 0017 réservait et que l'ADR 0013 laissait vide « tant
que ce ticket n'est pas ouvert ». C'est la moitié manquante : sans réalisé,
aucune erreur récente n'est mesurable.
inference/publication.py— dépose un.prompour le collecteur textfile denode-exporter, motif de
pg-backup.sh(#42), y compris sonchmod 0644: en0640 node-exporter ne lit pas et l'alerte se déclenche sur une absence.
rules.yaml— sixième alerte, sur le verdict 2 tenu deux heures.noDataState: OK: une dérive non mesurée n'est pas une dérive.Seuils déclarés (critère 2), surchargeables sans toucher au code :
ENERVISION_INFERENCE_DERIVE_{DEPLACEMENT_MAX,DISPERSION_MAX,ERREUR_MAX,
COUPLES_MINIMUM}.
Deux corrections qu'un essai contre le vrai serveur a imposées :
valeur_kwqui n'existe pas ; l'agrégat portemoyenne_kw, la cible même du modèle. Un test verrouille le nom, parce queviser
max_kwserait passé sans rien casser en comparant une prévision demoyenne à un maximum ;
numericenDecimal: un filtre naïf sur(int, float) aurait laissé l'erreur « non mesurée » pour toujours, sans
message. mypy strict a forcé à regarder ce que le pilote rend vraiment.
test_job_inferencelisaitcaplog.recordsen entier alors que son propreat_levelviseinference.emission: n'importe quel avertissement d'un autremodule le cassait. Filtré sur le journal qu'il annonce.
Ce que ça change
Closes #
Preuve
1. La dégradation propre quand la version promue n'a pas de référence.
La v6 date d'avant ce ticket : elle porte
modele_maemais aucune métrique dedistribution. La passe le dit et continue de prévoir.
2.
valeur_reference_kwse remplit pour la première fois. La colonneattendait ce ticket depuis la migration 0017.
3. Critère 1 — une dérive simulée sur les entrées déclenche, sans réalisé.
4. Critère 3 — le fichier déposé pour node-exporter. Répertoire vérifié
accessible en écriture à
deploy(drwxrwxr-x deploy:deploy), fichier en 0644.5. Les six règles Grafana se chargent. Une règle mal formée empêcherait
Grafana de charger toutes les alertes.
Relecture
Ce qui suit le code
docs/runbooks/mis à jour, un geste d'exploitation a changédocs/adr/complété, une décision structurante a été priseOù regarder en priorité
Relu en entier, y compris les 35 cas. Ce qui suit est une demande de changement sur un seul point ; le reste tient.
Ce qui est juste et mérite d'être nommé : la séparation entrée/erreur, la référence figée à la promotion avec refus explicite d'une comparaison entre versions, les trois verdicts plutôt que deux, toutes les divisions gardées (écart-type nul,
n < 2, MAE de référence nulle), la dispersion rendue symétrique, l'isolation totale de la passe, et la dérive mesurée après l'écriture sur la même connexion. Le choix de recopier plutôt que de joindre est argumenté et je le partage.Ce qui bloque : la surveillance annonce « stable » quand elle a cessé de mesurer
publication.publiern'est atteint que siecartexiste. Les deux sorties anticipées desurveiller_deriverendentNonesans rien publier :if reference is None: return Noneexcept Exception: journal.warning(...); return NoneOr le fichier
.promreste sur le disque avec ses dernières valeurs. Donc si MLflow devient injoignable, si l'exécution promue perd sa référence, ou si la requête tombe :ev_ia_derive_verdict = 0noDataState: OK, donc elle ne se déclenche pas non pluswarningque personne ne litAucune des sept séries ne porte d'horodatage : il n'existe pas de
ev_ia_derive_derniere_mesure_timestamp_seconds.Une surveillance qui annonce « stable » alors qu'elle a cessé de mesurer est pire que pas de surveillance, parce qu'elle fait cesser de regarder ailleurs.
C'est la leçon que le #228 a encodée ce matin, dans
bin/_metriques-ops.sh, et je te la cite parce qu'elle est écrite pour ce cas exactement :Ce module fait l'inverse : il ne réécrit rien du tout, donc rien ne vieillit.
Ce que je propose, et c'est court :
ev_ia_derive_derniere_mesure_timestamp_secondsà chaque tentative, y compris quand elle échoue — donc sortir la publication du chemin heureux ;ordu verdict :noDataState: OK: il couvre le cas « pas encore de mesure », pas le cas « la mesure s'est arrêtée ». Ce sont deux choses différentes et ton commentaire a raison sur la première.Deux points que je ne bloque pas
Le commentaire du SQL de recopie est faux, et le vrai garde-fou est ailleurs.
horodatageest le début du seau visé. À 14 h 35,p.horodatage < 14:35est vrai pour le seau de 14 h 00, qui n'est pas terminé. Ce qui sauve, c'est quemesure_horairene matérialise que des seaux complets — pas cette condition.Ce n'est pas théorique. Si quelqu'un passe l'agrégat en
materialized_only = falsepour gagner en fraîcheur — la tentation est réelle, on vient de resserrer sa latence au #205 — la recopie fige une heure partielle comme « réalisé », définitivement, puisqueis nullinterdit toute réécriture. La mesure de dérive serait corrompue sans que rien ne le dise.Une ligne de commentaire suffit à protéger le prochain : dire que la sûreté vient de la matérialisation par seaux complets, et que la toucher casse ceci.
Le fichier temporaire échappe au purgeur d'orphelins.
tempfile.NamedTemporaryFile(prefix="ev_ia_derive.prom.")produitev_ia_derive.prom.a8f3c2. Le purgeur du collecteur, repris par_metriques-ops.sh, ne balaie que*.prom.[0-9]*— la convention vient du$$du shell, qui est numérique. Un suffixe commençant par une lettre n'est jamais nettoyé.Deux choses mécaniques
Conflit sur
rules.yaml, contre les dix alertes du #228 fusionnées ce matin. Un rebase suffit.Le rouge de la chaîne est périmé. Aucun « Contrôles statiques » n'a échoué dans les cinq dernières exécutions de ce job ; les deux plus récentes rendent
Job succeeded. Le rebase relancera tout et l'effacera.Un point d'organisation
Le #117 est assigné à Justine et c'est toi qui l'as fait. Aucun problème sur le fond, mais il faut réassigner le ticket, sinon le backlog raconte autre chose que la forge.
Corrige le premier point et je fusionne dans la foulée.