Le jeu de référence est corrigé un mardi : trois étiquettes fausses sont rectifiées dans reference.jsonl, le fichier est recopié au même chemin sur Cloud Storage, le pipeline est relancé. Il termine en quarante secondes avec les mêmes scores qu'hier. Rien n'a été réévalué : Vertex AI a servi les résultats de la veille depuis son cache, parce que, de son point de vue, rien n'avait changé.
Le mécanisme
Avant d'exécuter une tâche, Vertex AI Pipelines cherche dans ML Metadata une exécution passée de même interface : mêmes valeurs de paramètres d'entrée, mêmes identifiants d'artefacts d'entrée, même définition des sorties, même spécification du composant (image, commande, arguments), et même nom de pipeline. S'il la trouve, il réutilise ses sorties et saute l'étape.
Le contenu d'un fichier désigné par son chemin n'entre pas dans cette clé. source_path="/gcs/.../reference.jsonl" est une chaîne : tant qu'elle ne change pas, prepare-dataset est un succès en cache, et toutes les tâches en aval reçoivent le même artefact, donc sont en cache elles aussi.
flowchart LR
subgraph Cle["Clé de cache de prepare-dataset"]
K1["source_path (texte)"]
K2["source_sha (texte)"]
K3["labels (liste)"]
K4["image + commande"]
end
F[("reference.jsonl modifié")] -- "ne change pas" --> K1
F -- "change" --> K2
Cle --> H{"exécution identique<br/>dans ML Metadata ?"}
H -- "oui" --> C["sorties réutilisées"]
H -- "non" --> R["étape exécutée"]
La parade du projet : run.py calcule l'empreinte du fichier local et la passe comme paramètre source_sha. Le fichier change, le paramètre change, la clé change. Le composant vérifie en plus que le fichier lu sur Cloud Storage est bien celui qui a été soumis, ce qui attrape un make cloud-up oublié.
La mise en œuvre
def file_sha(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()[:16]
source_path=source_path, source_sha=file_sha(HERE / "data" / "reference.jsonl"),actual = hashlib.sha256(raw).hexdigest()[:16]
if source_sha and actual != source_sha:
raise ValueError(f"le fichier lu ({actual}) n'est pas celui soumis ({source_sha})")Trois réglages de cache cohabitent :
| Niveau | Où | Ici |
|---|---|---|
| Tâche | task.set_caching_options(False) | load-production (le registre change entre deux exécutions) et promote (un effet de bord ne se met pas en cache) |
| Exécution | PipelineJob(enable_caching=...) | variable ENABLE_CACHING de .env, true par défaut |
| Défaut | ni l'un ni l'autre | cache actif pour les autres tâches |
Avec ces réglages, relancer le pipeline sans rien changer ne réévalue que la production si son prompt a changé entre-temps ; un candidat déjà mesuré sur le même jeu n'est pas repayé.
Les paramètres du pipeline sont sa surface de contrôle : prompt et version candidats, modèle, seuils, chemins. Ils sont enregistrés avec chaque exécution et apparaissent dans Vertex AI Experiments (param.min_accuracy, param.candidate_version…), si bien qu'une promotion faite avec un seuil abaissé se voit. Le prompt est passé en texte, pas en chemin, pour la même raison que source_sha : son contenu entre dans la clé de cache et dans l'historique.
La planification relance le pipeline à heure fixe, par exemple chaque nuit pour détecter qu'une révision du modèle a fait baisser la production :
job = aiplatform.PipelineJob(display_name=PIPELINE_NAME, template_path=template,
pipeline_root=f"gs://{b}/root", parameter_values=params(...),
enable_caching=False, labels=LAB_LABEL)
schedule = job.create_schedule(
display_name="lab-vertex-ai-pipelines-nuit",
cron="TZ=Europe/Paris 0 3 * * *", # 3 h chaque nuit
max_concurrent_run_count=1, # jamais deux portes en parallèle
max_run_count=30, # un mois, puis la planification s'arrête
)Une planification nocturne désactive le cache : son but est précisément de remesurer. Elle ne fait pas partie de make e2e (elle créerait une ressource récurrente) ; si vous la créez, schedule.delete() avant make cloud-down.
La notification passe par un dsl.ExitHandler, qui exécute sa tâche de sortie même quand le pipeline échoue. Avec NOTIFY_EMAIL dans .env, la compilation enveloppe le DAG :
if notify_email: # ExitHandler : la tâche de sortie tourne même si le pipeline échoue
with dsl.ExitHandler(VertexNotificationEmailOp(recipients=[notify_email])):
_gate(**args)Résultat obtenu (2026-10-16)
$ make test
............ [100%]
12 passed in 0.74sLe test test_prepare_refuses_file_changed_since_submit fait échouer prepare_dataset quand l'empreinte soumise ne correspond pas au fichier lu ; test_notification_wraps_in_exit_handler vérifie la présence de exit-handler-1 et de la tâche de notification dans le YAML compilé.
Ce qui casse en production
- Données modifiées au même chemin. Le scénario d'ouverture. Symptôme : exécution très rapide, scores identiques à la veille, tâches marquées « en cache » dans la console. Parade : une empreinte du contenu en paramètre, ou un chemin versionné (
data/2026-10-16/reference.jsonl). - Effet de bord mis en cache. Une tâche qui écrit dans un système externe (registre, base, ticket) et dont la clé n'a pas changé est sautée : rien n'est écrit, tout est vert. Parade :
set_caching_options(False)sur chaque tâche à effet de bord, vérifié par un test sur le YAML. - Modèle désigné par un alias mobile.
gemini-2.5-flashpeut changer de révision côté Google ; la clé de cache, elle, ne change pas. Parade : désactiver le cache pour les exécutions planifiées, ou épingler une version datée du modèle si le fournisseur en propose. - Planification oubliée. Une planification créée pour un test tourne chaque nuit pendant des mois. Parade :
max_run_count, un labelformation-lab, et la liste des planifications dansmake cloud-statussi vous en créez.
La règle de décision
Laissez le cache actif pour les étapes pures et coûteuses (évaluations), désactivez-le pour les lectures d'état et les écritures. Faites entrer dans la clé tout ce qui change le résultat : contenu des données, texte du prompt, nom du modèle. Planifiez une réévaluation sans cache si le modèle peut évoluer sous vous.
Pratique
Vous relancez le pipeline après avoir modifié uniquement prompts/v2.txt. Quelles tâches s'exécutent vraiment, cache actif ?
Le prompt candidat est un paramètre texte : sa modification change la clé de evaluate-prompt seulement. La production réévaluée a le même prompt et le même artefact de jeu qu'hier : cache. compare-versions reçoit un nouvel artefact metrics, donc s'exécute. promote n'est jamais en cache.
Une exécution planifiée chaque nuit, cache actif, affiche depuis trois semaines exactement le même score de production. Que soupçonnez-vous ?
Avec des paramètres et des artefacts identiques chaque nuit, le cache réutilise la première exécution. Un score rigoureusement identique à température 0 sur plusieurs semaines est le signe d'une mesure qui n'a pas eu lieu. Une planification de surveillance désactive le cache.
Exercice. Modifiez params() pour que la clé de cache de l'évaluation change aussi quand le modèle change de nom, sans toucher aux composants. Est-ce déjà le cas ?
Voir la solution
C'est déjà le cas : candidate_model est un paramètre de evaluate_prompt, et la production passe prod.outputs["model"], lu dans le registre. Changer MODEL dans .env change la valeur du paramètre, donc la clé. Ce qui n'est pas couvert, c'est un changement de révision derrière un même nom de modèle : seule la désactivation du cache (ou un nom de modèle daté) le couvre.
- Je sais ce qui entre dans la clé de cache de Vertex AI Pipelines, et ce qui n'y entre pas.
- Je fais entrer l'empreinte des données dans les paramètres.
- Je désactive le cache des lectures d'état et des tâches à effet de bord.
- Je planifie une réévaluation sans cache, bornée en nombre d'exécutions.