fix(assets): le test de reproductibilité ne vidait pas le cache ComfyUI

Correction d'une preuve qui n'en était pas une.

Ma vérification lançait le même plan deux fois et comparait les
empreintes. Le porteur ayant indiqué où ComfyUI écrit ses images, le
dossier a pu être inspecté : UN SEUL fichier y figurait alors que deux
exécutions avaient été lancées.

ComfyUI met en cache le résultat d'un graphe identique. La « deuxième
exécution » n'avait rien recalculé — elle avait relu la première.
Comparer deux empreintes dans ces conditions ne mesure que le cache.

Après POST /free, une exécution authentique produit decor_00002_.png,
bit-identique au premier. La conclusion était juste, la méthode ne
l'était pas.

Le test devient un mode de l'outil plutôt qu'une manipulation :
--reproductibilite génère, vide le cache, régénère et compare. Un test
de reproductibilité qui ne vide pas le cache se ment à lui-même.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
OKI
2026-07-27 12:25:52 -04:00
co-authored by Claude Opus 5
parent bd29770bd2
commit eaaf7c34ad
4 changed files with 120 additions and 9 deletions
+27 -8
View File
@@ -60,16 +60,35 @@ serait mettre la charrue avant les bœufs.
Le porteur a ouvert l'accès à son instance (`127.0.0.1:8188`, ComfyUI 0.27.1, deux GPU
AMD). Le pipeline a donc été exécuté pour de vrai, pas simulé.
### Reproductibilité prouvée au bit près
### Reproductibilité prouvée — après correction d'un test qui ne prouvait rien
| Exécution | seed | SHA-256 de l'image |
|---|---|---|
| 1 | 3475989105 | `b7007a7a1088cb359a06ddaaff358e92…` |
| 2 | 3475989105 | `b7007a7a1088cb359a06ddaaff358e92…` |
**Images bit-identiques**, `sha256 b7007a7a1088cb359a06ddaaff358e92…`, sur deux exécutions
séparées par un **vidage du cache**. Workflow de référence : `workflows/decor-sdxl.json`
(SDXL 1.0, 1024×576, 20 pas, dpmpp_2m/karras).
**Images bit-identiques.** C'est précisément ce que le pack d'origine annonçait sans y
parvenir, et c'est maintenant vérifié plutôt qu'affirmé. Workflow de référence :
`workflows/decor-sdxl.json` (SDXL 1.0, 1024×576, 20 pas, dpmpp_2m/karras).
Le mot « après vidage du cache » est tout le sujet, et il a failli manquer.
Ma première vérification lançait simplement le même plan deux fois et comparait les
empreintes. Le porteur ayant indiqué où ComfyUI écrit ses images
(`/media/sucupira/DKXONE/ComfyUI/output`), le dossier a pu être inspecté : **un seul
fichier y figurait** alors que deux exécutions avaient été lancées.
ComfyUI met en cache le résultat d'un graphe identique. Ma « deuxième exécution » n'avait
donc rien recalculé — elle avait relu la première. Comparer deux empreintes dans ces
conditions ne mesure que le bon fonctionnement du cache.
Après `POST /free`, une exécution authentique a produit `decor_00002_.png`, bit-identique
au premier. **La conclusion était juste ; la méthode ne l'était pas.**
Le test est désormais un mode de l'outil, pas une manipulation à la main :
```bash
python3 comfyui_client.py --workflow workflows/decor-sdxl.json \
--plan plan-assets.json --reproductibilite
```
Il génère, vide le cache, régénère et compare. Un test de reproductibilité qui ne vide pas
le cache est un test qui se ment à lui-même.
### Deux défauts de mon propre client, révélés par le réel
+2 -1
View File
@@ -25,7 +25,8 @@
"fetch:medias": "node tools/data/fetch-medias.mjs",
"verify:seeds": "python3 tools/assets/comfyui_client.py --verifier",
"verify:candidats": "node tools/data/verify-candidats.mjs",
"audit": "node tools/build/audit.mjs"
"audit": "node tools/build/audit.mjs",
"verify:reproductibilite": "python3 tools/assets/comfyui_client.py --workflow tools/assets/workflows/decor-sdxl.json --plan tools/assets/plan-assets.json --reproductibilite"
},
"devDependencies": {
"@fontsource/archivo": "^5.3.0",
+24
View File
@@ -15,12 +15,23 @@ Un enfant qui apprend dessus apprend l'hallucination d'un modèle.
Le décor, lui, n'a rien à enseigner. C'est là que la génération a sa place.
## Où ComfyUI écrit
Sur la machine du porteur : `/media/sucupira/DKXONE/ComfyUI/output`, sous-dossier `ki-fwi/`
(fixé par `filename_prefix` dans le workflow). Le client rapatrie les images par l'API
`/view`, ce qui fonctionne aussi si ComfyUI tourne ailleurs — mais inspecter ce dossier
reste le seul moyen de savoir si une génération a **réellement** eu lieu.
## Utilisation
```bash
# Vérifier que les seeds sont bien déterministes (aucun serveur requis)
python3 comfyui_client.py --verifier
# Test de reproductibilité RÉEL : génère, vide le cache, régénère, compare
python3 comfyui_client.py --workflow workflows/decor-sdxl.json \
--plan plan-assets.json --reproductibilite
# Calculer les seeds d'un plan sans rien générer
python3 comfyui_client.py --workflow workflows/decor.json --plan plan-assets.json --sec
@@ -80,3 +91,16 @@ c'est elle qui garantit qu'une régénération donne la même image.
Il ne valide rien. Tout asset sort en `valide: false` et le reste jusqu'à ce qu'un humain
tranche — comme pour les photographies. La différence est qu'un asset de filière B, même
validé, ne pourra jamais porter une épreuve.
## Piège du cache d'exécution
**ComfyUI met en cache le résultat d'un graphe identique.** Relancer le même workflow ne
recalcule rien : aucune image n'est écrite, et l'API renvoie la sortie précédente.
Conséquence : comparer deux « exécutions » sans vider le cache ne prouve rien sur la
reproductibilité — cela prouve seulement que le cache fonctionne. C'est l'erreur commise
puis corrigée en session 3.
`--reproductibilite` appelle `POST /free` entre les deux générations. Pour vérifier à la
main, regarder si un nouveau fichier est apparu dans le dossier de sortie : s'il n'y en a
pas, rien n'a été calculé.
+67
View File
@@ -71,6 +71,29 @@ def empreinte(chemin: Path) -> str:
return h.hexdigest()
def vider_cache(serveur: str) -> None:
"""
Vide le cache d'exécution de ComfyUI.
INDISPENSABLE avant tout test de reproductibilité. ComfyUI met en cache le résultat
d'un graphe identique : relancer le même workflow ne recalcule rien et renvoie la
sortie précédente. Comparer deux « exécutions » sans vider le cache ne prouve donc
rien du tout — seulement que le cache fonctionne.
Constaté en session 3 : la deuxième exécution n'avait écrit aucun fichier dans le
dossier de sortie de ComfyUI. Le hasard a voulu que la conclusion soit juste ; la
méthode, elle, était fausse.
"""
corps = json.dumps({"unload_models": True, "free_memory": True}).encode("utf-8")
requete = urllib.request.Request(
urllib.parse.urljoin(serveur, "/free"),
data=corps,
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(requete, timeout=30):
pass
def poster(serveur: str, workflow: dict, client_id: str) -> str:
"""Envoie un workflow à ComfyUI et renvoie l'identifiant de la tâche."""
corps = json.dumps({"prompt": workflow, "client_id": client_id}).encode("utf-8")
@@ -190,6 +213,41 @@ def generer(args: argparse.Namespace) -> int:
return 0
def reproductibilite(args: argparse.Namespace) -> int:
"""
Test de reproductibilité RÉEL : génère, vide le cache, régénère, compare.
Sans le vidage intermédiaire, ce test ne mesure que le cache de ComfyUI.
"""
args_gen = argparse.Namespace(**vars(args))
args_gen.sec = False
print("— première génération")
generer(args_gen)
premier = json.loads((SORTIE / "manifeste.json").read_text(encoding="utf-8"))
print("\n— vidage du cache d'exécution")
vider_cache(args.serveur)
print("\n— seconde génération, cache vidé")
generer(args_gen)
second = json.loads((SORTIE / "manifeste.json").read_text(encoding="utf-8"))
identiques = True
print()
for a, b in zip(premier, second):
egal = a["sha256"] == b["sha256"]
identiques &= egal
print(f"{'' if egal else ''} {a['cle']} — seed {a['seed']}{a['sha256'][:24]}")
print(
"\n✔ Sorties bit-identiques après vidage du cache : la reproductibilité est réelle."
if identiques
else "\n✘ Sorties divergentes : la reproductibilité n'est PAS acquise."
)
return 0 if identiques else 1
def verifier(_args: argparse.Namespace) -> int:
"""
Contrôle que les seeds sont bien déterministes.
@@ -218,10 +276,19 @@ def main() -> int:
parseur.add_argument("--serveur", default=COMFY_DEFAUT)
parseur.add_argument("--sec", action="store_true", help="calcule les seeds sans appeler ComfyUI")
parseur.add_argument("--verifier", action="store_true", help="teste le déterminisme des seeds")
parseur.add_argument(
"--reproductibilite",
action="store_true",
help="génère deux fois en vidant le cache entre les deux, et compare les empreintes",
)
args = parseur.parse_args()
if args.verifier:
return verifier(args)
if args.reproductibilite:
if not args.workflow or not args.plan:
parseur.error("--workflow et --plan sont requis pour --reproductibilite")
return reproductibilite(args)
if not args.workflow or not args.plan:
parseur.error("--workflow et --plan sont requis (ou --verifier)")
return generer(args)