thefinalboss commited on
Commit
bbb409c
·
verified ·
1 Parent(s): 40f1edd

Checker: gen_quality_metrics.py + watch_gen_quality_bg1.sh (scripts only, add-only)

Browse files
v3_scale/gen/gen_quality_metrics.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Checker worker (Toronto, 24 oct. 2026, 12 h 30) — post-traitement LECTURE SEULE des sorties de gen_eval.py (ne relance aucun modèle).
3
+ Entrée : samples/<tag>__<décodage>__<prompt>.txt (PROMPT / VRAI / GÉNÉRÉ, écrits par gen_eval.py) + results_gen_v1.jsonl (contrôle croisé).
4
+ Par défaut : décodage fused, 5 prompts en1 en2 fr1 fr2 code (= gen_5p_report.py), tags = clés de report_5p.json ayant des samples.
5
+ Métriques (sur le texte GÉNÉRÉ, 200 o) :
6
+ - valid_word_rate : mots (≥2 lettres, même regex que gen_eval) présents dans lexicon_v3.json (vocabulaire de corpus_v3/train.bin, fréq ≥3) ;
7
+ vérifié = valid_word_rate de results_gen_v1.jsonl (même texte, même lexique).
8
+ - répétition : distinct-1/2/3 sur les mots (types/occurrences), rep_word3 = part des trigrammes de mots déjà vus, rep_byte8 (= rep_rate8 gen_eval),
9
+ distinct_byte4.
10
+ - langue/code : part FR des mots-outils (FR/(FR+EN), listes de gen_eval), ratio de caractères de code []{}()=;_#<>, étiquettes lang/dom ;
11
+ DÉRIVE prompt→génération (étiquette changée ; |Δ part FR| ; Δ ratio code) ET génération vs vraie continuation (comme gen_eval).
12
+ Sortie : agrégats seulement (aucun texte) -> gen_quality_metrics.json (+ .md) dans checker/gen/, ou OUTP=chemin_sans_extension.
13
+ Usage : gen_quality_metrics.py [TAG ...] (env : DEC=fused, SAMPLES=..., IN=..., OUTP=...)"""
14
+ import os, sys, json, re
15
+ D = os.path.dirname(os.path.abspath(__file__)); P5 = ["en1", "en2", "fr1", "fr2", "code"]
16
+ DEC = os.environ.get("DEC", "fused"); SAM = os.environ.get("SAMPLES", f"{D}/samples"); IN = os.environ.get("IN", f"{D}/results_gen_v1.jsonl")
17
+ OUTP = os.environ.get("OUTP", f"{D}/gen_quality_metrics")
18
+ LEX = set(json.load(open(f"{D}/lexicon_v3.json")))
19
+ FR = set("le la les de des du un une et est que qui dans pour pas sur au aux il elle ne se ce son sa ses avec par plus mais".split())
20
+ EN = set("the of and to a in is that it was for on with as his her he she they not but at by be this had".split())
21
+ WRE = re.compile(r"[a-zà-ÿœæç']+"); CODE = set("[]{}()=;_#<>")
22
+ def words(t): return [w.strip("'") for w in WRE.findall(t.lower()) if len(w.strip("'")) >= 2]
23
+ def fr_en(t): ws = words(t); return sum(w in FR for w in ws), sum(w in EN for w in ws)
24
+ def lang(t): f, e = fr_en(t); return "fr" if f > e else ("en" if e > f else "?")
25
+ def fr_share(t): f, e = fr_en(t); return f / (f + e) if f + e else None
26
+ def code_ratio(t): return sum(c in CODE for c in t) / len(t) if t else 0.0
27
+ def dom(t): return "code" if t and code_ratio(t) >= 0.04 else "prose"
28
+ def distinct(seq, n):
29
+ g = [tuple(seq[i:i + n]) for i in range(len(seq) - n + 1)]; return (len(set(g)) / len(g)) if g else None
30
+ def rep_rate(seq, n):
31
+ seen, r, tot = set(), 0, 0
32
+ for i in range(len(seq) - n + 1):
33
+ s = tuple(seq[i:i + n]) if isinstance(seq, list) else seq[i:i + n]; tot += 1; r += s in seen; seen.add(s)
34
+ return r / tot if tot else None
35
+ def parse(path):
36
+ t = open(path, encoding="utf-8").read()
37
+ a = t.index("PROMPT:\n"); b = t.index("\n\nVRAI:\n"); c = t.index("\n\nGÉNÉRÉ:\n")
38
+ return t[a + 8:b], t[b + 8:c], t[c + 10:].removesuffix("\n")
39
+ REF = {}
40
+ if os.path.exists(IN):
41
+ for l in open(IN):
42
+ r = json.loads(l)
43
+ if r["decoding"] == DEC: REF[(r["tag"], r["prompt"])] = r
44
+ tags = sys.argv[1:]
45
+ if not tags:
46
+ rp = f"{D}/report_5p.json"; base = list(json.load(open(rp))) if os.path.exists(rp) else sorted({k[0] for k in REF})
47
+ tags = [t for t in base if all(os.path.exists(f"{SAM}/{t}__{DEC}__{p}.txt") for p in P5)]
48
+ r3 = lambda x: None if x is None else round(x, 4)
49
+ out = {"script": os.path.abspath(__file__), "decoding": DEC, "prompts": P5, "lexicon": "lexicon_v3.json (corpus_v3 train, fréq≥3, %d mots)" % len(LEX), "tags": {}}
50
+ rows = ["| tag | mots valides | distinct-1 | distinct-2 | rep. trigr. mots | rep. 8-gr. octets | dérive langue prompt→gén | dérive domaine prompt→gén | dérive langue vs vrai | contrôle gen_eval |",
51
+ "|---|---|---|---|---|---|---|---|---|---|"]
52
+ for t in tags:
53
+ per = {}
54
+ for p in P5:
55
+ pr, tr, g = parse(f"{SAM}/{t}__{DEC}__{p}.txt"); ws = words(g); gb = g.encode("utf-8")
56
+ fp, fg = fr_share(pr), fr_share(g)
57
+ m = {"n_words": len(ws), "valid_word_rate": r3(sum(w in LEX for w in ws) / max(len(ws), 1)),
58
+ "distinct_1": r3(distinct(ws, 1)), "distinct_2": r3(distinct(ws, 2)), "distinct_3": r3(distinct(ws, 3)),
59
+ "rep_word3": r3(rep_rate(ws, 3)), "rep_byte8": r3(rep_rate(gb, 8)), "distinct_byte4": r3(distinct(list(gb), 4)),
60
+ "lang_prompt": lang(pr), "lang_gen": lang(g), "lang_true": lang(tr), "dom_prompt": dom(pr), "dom_gen": dom(g), "dom_true": dom(tr),
61
+ "fr_share_prompt": r3(fp), "fr_share_gen": r3(fg), "fr_share_abs_delta": r3(abs(fp - fg)) if fp is not None and fg is not None else None,
62
+ "code_ratio_prompt": r3(code_ratio(pr)), "code_ratio_gen": r3(code_ratio(g)), "code_ratio_delta": r3(code_ratio(g) - code_ratio(pr)),
63
+ "lang_drift_prompt_gen": lang(g) != lang(pr), "dom_drift_prompt_gen": dom(g) != dom(pr),
64
+ "lang_drift_true_gen": lang(g) != lang(tr), "dom_drift_true_gen": dom(g) != dom(tr)}
65
+ ref = REF.get((t, p))
66
+ m["xcheck_gen_eval"] = None if ref is None else {k: ref[k] == m[k2] if isinstance(ref[k], str) else abs(ref[k] - (m[k2] or 0)) < 1e-3
67
+ for k, k2 in (("valid_word_rate", "valid_word_rate"), ("rep_rate8", "rep_byte8"), ("lang_gen", "lang_gen"), ("lang_true", "lang_true"), ("dom_gen", "dom_gen"))}
68
+ per[p] = m
69
+ mean = lambda k: r3(sum(per[p][k] for p in P5 if per[p][k] is not None) / max(1, sum(per[p][k] is not None for p in P5)))
70
+ cnt = lambda k: sum(per[p][k] for p in P5)
71
+ xc = [v for p in P5 if per[p]["xcheck_gen_eval"] for v in per[p]["xcheck_gen_eval"].values()]
72
+ o = {"valid_word_rate_mean": mean("valid_word_rate"), "distinct_1_mean": mean("distinct_1"), "distinct_2_mean": mean("distinct_2"),
73
+ "distinct_3_mean": mean("distinct_3"), "rep_word3_mean": mean("rep_word3"), "rep_byte8_mean": mean("rep_byte8"), "distinct_byte4_mean": mean("distinct_byte4"),
74
+ "lang_drift_prompt_gen_n": cnt("lang_drift_prompt_gen"), "dom_drift_prompt_gen_n": cnt("dom_drift_prompt_gen"),
75
+ "lang_drift_true_gen_n": cnt("lang_drift_true_gen"), "dom_drift_true_gen_n": cnt("dom_drift_true_gen"),
76
+ "fr_share_abs_delta_mean": mean("fr_share_abs_delta"), "xcheck_gen_eval_ok": f"{sum(xc)}/{len(xc)}", "per_prompt": per}
77
+ out["tags"][t] = o
78
+ rows.append(f"| {t} | {100*o['valid_word_rate_mean']:.1f} % | {o['distinct_1_mean']:.3f} | {o['distinct_2_mean']:.3f} | {100*o['rep_word3_mean']:.1f} % | {100*o['rep_byte8_mean']:.1f} % | {o['lang_drift_prompt_gen_n']}/5 | {o['dom_drift_prompt_gen_n']}/5 | {o['lang_drift_true_gen_n']}/5 | {o['xcheck_gen_eval_ok']} |")
79
+ json.dump(out, open(OUTP + ".json", "w"), indent=1, ensure_ascii=False); open(OUTP + ".md", "w").write("\n".join(rows) + "\n"); print("\n".join(rows))
v3_scale/gen/watch_gen_quality_bg1.sh ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Checker worker (Toronto, 24 oct. 2026, 12 h 47) — veilleur LÉGER, lecture seule de la chaîne (ne modifie aucun script de chaîne, ne lance aucun modèle).
3
+ # Attend (sondage 120 s) : (1) les 5 samples/bg1_m8__fused__{en1,en2,fr1,fr2,code}.txt (écrits par gen_eval.py via watch_scale.sh),
4
+ # (2) le rapport génération de la chaîne : bg1_m8 dans scale/logs/chain_post_m8_2245.state ET report_5p.json[bg1_m8] complet.
5
+ # Puis UNE fois : nice 10, OMP_NUM_THREADS=1, ulimit -v 3,5 GiB : gen_quality_metrics.py bg1_m8 -> gen_quality_metrics_bg1_m8.{json,md}
6
+ # (agrégats/métriques numériques seulement, aucun texte), et sort. Abandon après 48 h. Journal : logs/watch_gen_quality_bg1.log.
7
+ G=/workspace/spira_shared/checker/gen; S=/workspace/spira_shared/checker/scale; PY=/workspace/SPIRA-venv/bin/python; TAG=bg1_m8
8
+ LOG=$G/logs/watch_gen_quality_bg1.log; ST=$S/logs/chain_post_m8_2245.state; MAXW=$((48*3600)); T0=$(date +%s)
9
+ export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1
10
+ log(){ echo "$(TZ=America/Toronto date '+%F %H:%M:%S %Z') [watch_gen_quality_bg1 pid $$] $*" >> $LOG; }
11
+ exec 9>$G/logs/watch_gen_quality_bg1.lock; flock -n 9 || { log "déjà actif, sortie"; exit 1; }
12
+ log "démarré ; attend samples/${TAG}__fused__{en1,en2,fr1,fr2,code}.txt + rapport gén. (state + report_5p.json)"
13
+ ready(){
14
+ for p in en1 en2 fr1 fr2 code; do [ -s $G/samples/${TAG}__fused__$p.txt ] || return 1; done
15
+ grep -qx "$TAG" $ST 2>/dev/null || return 1
16
+ $PY -c "import json,sys;d=json.load(open('$G/report_5p.json')).get('$TAG');sys.exit(0 if d and d.get('status')!='incomplet' and d.get('n_prompts')==5 else 1)" 2>/dev/null
17
+ }
18
+ while :; do
19
+ if ready; then
20
+ log "prêt -> gen_quality_metrics.py $TAG"
21
+ ( ulimit -v 3670016; cd $G && OUTP=$G/gen_quality_metrics_${TAG} nice -n 10 $PY $G/gen_quality_metrics.py $TAG ) >> $LOG 2>&1; rc=$?
22
+ log "gen_quality_metrics EXIT $rc -> gen_quality_metrics_${TAG}.json/.md"; exit $rc
23
+ fi
24
+ [ $(( $(date +%s) - T0 )) -ge $MAXW ] && { log "abandon après 48 h (non prêt)"; exit 2; }
25
+ sleep 120
26
+ done