Skip to content

Feat(audio,#19739): garde d'attaque CV3 en ligne dans la boucle p5 -- re-roll cible sur perte de tete - #23476

Merged
jsboige merged 5 commits into
mainfrom
feature/audio-attack-guard-inline
Oct 31, 2026
Merged

jsboige merged 5 commits into
mainfrom
feature/audio-attack-guard-inline

Conversation

@jsboige

@jsboige jsboige commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Grain: DEEP/genai — lane myia-po-2024:CoursIA-2 — prev: MED/lean #23467

Ce que fait cette PR

Elle branche la garde d'attaque par chunk (v4.attack_guard, organe livré par #21459) en ligne dans la boucle de rendu CosyVoice3 de p5 (_synthesize_narrator_cosyvoice3) : un chunk qui passe le plancher de durée n'est accepté que si l'ASR entend ses premiers mots ; sinon, re-roll ciblé à la graine espacée — la même formule que le re-roll dégénéré (seed + i + attempt * 104_729, exactement attack_guard.spaced_seed).

C'est l'étape que le fil de #19739 nommait comme restante : l'organe savait dépister l'onset-drop et planifier le re-roll ; il manquait le branchement en boucle. La phase 1 (#19699) avait prouvé que la durée ne peut jamais détecter une attaque loupée (les bandes chars/s se recouvrent intégralement entre rendus propres et omettants) — seul l'ASR le peut.

Conception (les décisions qui engagent)

Décision Motif
Garde ON par défaut (V4_ATTACK_GUARD=off = bras A/B) La correction est le livrable de phase 2, pas un opt-in. off sert à la comparaison avant/après mesurée — ce n'est pas un mode de production : un run sans garde livre l'onset-drop que l'issue existe pour fermer.
ASR via l'organe attack_guard.asr_http (endpoint p7, localhost:8190, large-v3-turbo ; V4_ASR_BASE_URL pour la portabilité) La garde doit mesurer exactement ce que p7 mesure, pour qu'un verdict « attaque manquante » et un span d'omission p7 ne puissent pas diverger. Pas de second client ASR.
L'ASR ne court qu'après le plancher de durée Transcrire un rendu dégénéré de 0,04 s ne dit rien et coûte l'appel.
Un re-roll d'attaque consomme le budget _CV3_RENDER_ATTEMPTS (5) Même boucle, même economics ; un chunk sain sort toujours au tentatif 0.
Échec dur distinct : « attack still dropped after N attempts » nomme le verdict (reason, mots perdus) Jamais un MP3 accepté sans attaque : le run le compte en Failed — l'acceptation non gardée cacherait la perte.
Sentinel cache bumpé -v3 → -v4 Un segment que le run pré-garde a livré avec une attaque loupée est périmé sous l'acceptation gardée ; le sentinel force son re-rendu (convention #19692 : bump à chaque changement qui peut altérer l'audio d'un segment déjà rendu).

Mesures

Mesure Résultat
Suite v4 entière (pytest tests/, worktree frais sur origin/main) 216 passed, 0 échec, 0 régression (dont 22 tests CV3 narrateur existants, inchangés dans leurs assertions)
Nouvelle suite test_p5_cv3_attack_guard.py 9 tests : chunk sain accepté au tentatif 0 (graine d'origine, 1 appel ASR) · perte de tête → re-roll à graine espacée puis acceptation · attaque jamais récupérée → échec dur sans MP3 · off → 0 appel ASR · ASR seulement après plancher · indépendance par chunk (la graine porte l'index DU chunk) · défaut ON + valeurs off reconnues (les fautes de frappe fail-safe ON) · sentinel v4 · le résolveur réutilise bien l'organe et honore V4_ASR_BASE_URL
Suite existante CV3 _install_fake_runtime stubbe un verdict propre (la logique de verdict a ses propres suites) — la boucle de re-roll reste la seule sous test

Verdict SOTA — la jambe corpus est RECOVERABLE-MACHINE

La preuve hermétique ci-dessus est livrée ; la mesure corpus réelle (rendre + transcrire le corpus avec la garde en ligne, critères pré-enregistrés) exige l'env moteur. Mesuré sur ce poste : _runtime/CosyVoice absent, pretrained_models/Fun-CosyVoice3-0.5B absent, cache HF sans Fun-CosyVoice3-0.5B-2512 ni whisper-large-v3-turbo (seul whisper-tiny y est), tous les conteneurs GenAI DOWN — le banc render_corpus_attack_guard.py a explicitement un env render séparé (« GPU 2 » chez la machine qui porte les services). Router vers cette machine + re-exécuter :

  1. bras sans garde : V4_ATTACK_GUARD=off — run p5 borné sur les segments sondes (graines 4243-4245 de feat(genai,#19692): phase 1 -- routage narrateur independant du moteur + CosyVoice3 decoupe (drapeau, defaut inchange) + controle d'omission p7 #19699, onset-drop connu) ;
  2. bras avec garde : défaut ON — même run ;
  3. critères du pré-enregistrement (commentaire 6516437492 sur feat(audio,#19692): phase 2 -- garde d'attaque ASR par chunk (onset-drop CV3 mesure, porte #17586 non conforme) #19739, écrit AVANT toute mesure) : attaques manquantes par chunk avant → après (attendu > 0 → 0) et chunk sain inchangé au tentatif 0. Pas de verdict de stratégie.

Résolution du conflit avec main (tête 8103de36fd)

main a livré deux fois la garde d'attaque depuis l'ouverture de cette
branche :

Commit PR Portée
b63fbd7ba9 #22569 chemin CV3 (_synthesize_narrator_cosyvoice3) — off par défaut (NARRATOR_CV3_ATTACK_GUARD), helpers _cv3_chunk_asr / _cv3_chunk_attack_dropped
b20529e074 #23486 chemins narrateur Qwen — convention V4_ATTACK_GUARD (off l'emporte), os.getenv(ATTACK_GUARD_ENV, "on")

Le conflit de contenu portait sur v4/p5_tts.py : deux implémentations
concurrentes du même comportement dans la même fonction. Il est résolu en
gardant la version de cette branche, pour trois raisons mesurées :

  1. Convention unique. Feat(audio,#19739): garde d'attaque par chunk sur les chemins narrateur Qwen (VoiceDesign + CustomVoice) #23486 vient de standardiser V4_ATTACK_GUARD sur
    les chemins Qwen. Cette branche lit la même variable sur le chemin CV3
    (même constante V4_ASR_BASE_URL, même endpoint localhost:8190) : un
    seul interrupteur gouverne le pipeline v4, au lieu de deux variables
    concurrentes (NARRATOR_CV3_ATTACK_GUARD d'un côté, V4_ATTACK_GUARD de
    l'autre).
  2. Verdict délégué, pas ré-invoqué. _cv3_attack_verdict appelle
    attack_guard.evaluate_chunk (l'organe canonique, qui rend le verdict
    enrichi : reason, n_missing_leading, chunk_index) ; la version de
    Feat(genai,#19739): garde d'attaque par chunk en ligne dans la boucle de rendu CV3 #22569 rappelait detect_missing_attack directement et perdait le compte
    des mots perdus.
  3. Testabilité. L'ASR est résolu par segment (_resolve_attack_asr,
    callable injectable) au lieu d'un client figé à l'import : le câblage se
    teste hors réseau et hors GPU.

Les deux helpers de main (_cv3_chunk_asr, _cv3_chunk_attack_dropped)
sont supprimés dans cette tête : ils ne servaient plus qu'à la boucle CV3
(vérifié par grep sur l'arbre entier) et n'étaient référencés par aucun autre
module. Les 3 tests de #22569 qui les monkeypatchaient sont adaptés — ils
pilotent désormais la garde via _resolve_attack_asr + V4_ATTACK_GUARD, et
le stub de verdict propre de _install_fake_runtime est devenu un
défaut désactivable (guard_stub=False) : sans cela le stub répondait à
la place du test et l'onset-drop n'était jamais observé (échec réel mesuré :
assert 1 == 2 sur out.attempts).

Mesure après résolution : pytest v4/tests/ -k "p5 or attack_guard or p7_omission or qwen_chunk"
→ 118 passed, 0 échec (34 tests CV3 : 25 narrateur + 9 garde). Aucune
cellule de notebook touchée : H.3 reste trivial (0 cellule sans
execution_count sur les deux carnets de la série fusionnés).

Chevauchement mesuré : git merge-tree --write-tree entre la tête de
#23492 et la tête de cette PR rend rc=0, arbre propre — les deux PRs
touchent p5_tts.py mais sur des fonctions disjointes
(_synthesize_fishaudio_path d'un côté, _synthesize_narrator_cosyvoice3 de
l'autre). Plus aucune PR ouverte ne touche p5_tts.py.

Portée

v4/p5_tts.py (+ le stub dans la suite existante, + la nouvelle suite). attack_guard.py lui-même est inchangé — c'est le branchement de l'organe, pas sa révision.

Reprise du claim STALE de myia-po-2023:CoursIA-2 (posé 25/10, ~115 h sans activité ; check_lane_claim.py : reprise autorisée) — claim de remplacement posté : 6516437492.

See #19739 — l'issue n'est pas close par cette PR : il lui reste la jambe corpus mesurée. Part of #19692.

🤖 Generated with Claude Code

… re-roll cible sur perte de tete

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

@jsboige

jsboige commented Oct 30, 2026

Copy link
Copy Markdown
Owner Author

[INFO] rouge base-inherited sur la jambe Scripts & Notebook-Tools Tests — pas une introduction de cette PR

La jambe de tests est rouge sur cette PR (2 failed, 21895 passed, 353 skipped). Les deux échecs sont un seul et même défaut, hors du périmètre de ce diff :

  • test_audit_index_unique_and_no_identical_duplicates_per_pair — « prefixe NNNN duplique dans une paire » ;
  • le doublon d'index intra-révision sur la révision testée.

Le registre scripts/notebook_tools/twin_pairs.d/ porte cinq doublons d'index par paire sur main (gametheory-6-evolutiontrust 0033/0034/0035, gametheory-9-backwardinduction 0015/0016) : la PR ebacbf7b24 (lane myia-po-2027:CoursIA-2, fusionnée le 2026-10-30T06:10Z) a ouvert cinq attestations sur des index déjà pris par des attestations fusionnées antérieurement. Les dix fichiers en collision sont déjà sur origin/main ; aucune PR ouverte ne touche ces deux répertoires.

Le diff de cette PR porte v4/p5_tts.py et v4/tests/** — ni le registre, ni les carnets GameTheory-06 / GameTheory-09. La preuve que le défaut est bien de la base : le garde qui échoue passe sur la branche de réparation #23481 (5 files changed, 0 insertions(+), 0 deletions(-) — cinq git mv purs vers le premier index libre, le postérieur cédant l'index comme le prescrit scripts/notebook_tools/check_twin_index_collisions.py).

Demande au coordinateur : fusionner #23481 (ou rejouer cette jambe après son merge) — tant qu'il est pas fusionné, la jambe de tests rougit sur toute PR du dépôt, y compris celle-ci. Le PR gate de cette PR, lui, n'a qu'un minuteur DWELL à son compte (72 checks verts au relevé).

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #23476 (Feat(audio,#19739): garde d'attaque CV3 en ligne dans la boucle p5 -- re-roll cible sur perte de tete) 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.

Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur main. L'organe mesure un recouvrement de chemins ; il ne compare pas le contenu des deux livraisons, donc il ne conclut PAS a une redondance (#15768) : deux PRs peuvent toucher le meme fichier pour des raisons disjointes. L'arbitrage reste a la lane ou au coordinateur.

@github-actions github-actions Bot added pr-overlap Advisory: another open PR touches the same files (organ #13615) large-pr-no-review PR > seuil sans review (ni bot ni humaine) -- retire quand une review arrive (#11232) labels Oct 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Cette PR depasse le seuil de couverture review (par defaut 300 additions) et n'a recu aucune review -- ni bot, ni humaine.

Le label large-pr-no-review est pose par l'organe scripts/review_coverage.py porte par l'issue #11232. Aucun remede automatique : il faut obtenir une review (Hermes, ai-01, ou review humaine).

Le label est retire au balayage suivant (quotidien) des qu'une review arrive -- dans reviews[] ou en commentaire de verdict -- ou que le diff passe sous le seuil. Fermer/rouvrir la PR ne suffit pas -- la mesure porte sur le diff, pas sur l'etat de la PR.

Seuil, historique et exceptions : cf. docs/reference/review-coverage-threshold.md.

myia-ai-01 pushed a commit that referenced this pull request Oct 30, 2026
…ur Qwen (#23486)

Les deux chemins narrateur Qwen du pipeline v4 (VoiceDesign, CustomVoice)
concatenent des chunks de phrase comme le chemin CV3, et n'etaient pas
gardes : un chunk qui commence au milieu du texte perd son attaque, et
aucune mesure de duree ne le voit -- seule l'ASR le voit.

La garde d'attaque existante (v4.attack_guard, #21459) est branchee par
chunk et en ligne, sur le meme contrat d'echec dur que le chemin CV3
(#23476) : un chunk dont l'attaque n'a pas ete obtenue n'est jamais livre,
la passe est refusee. Le levier est le re-rendu -- le gateway ne prend
pas de graine et echantillonne a temperature 0.9.

La garde est fermee par defaut au niveau du client (un banc de mesure n'a
pas d'ASR a cote de lui) ; c'est le pipeline qui l'active, et
V4_ATTACK_GUARD=off reste le bras A/B.

212 tests v4 verts, dont 5 nouveaux.

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
…t p5_tts.py est resolu par supersession

main porte deux deliveries de la garde d'attaque (#22569 sur le chemin CV3,
#23486 sur les chemins narrateur Qwen) ; cette branche en porte une
implementation plus forte du meme comportement. Le conflit de contenu sur
`v4/p5_tts.py` est resolu en gardant la version de cette branche, qui :

- lit la convention `V4_ATTACK_GUARD` (`off` l'emporte) -- la meme que
  celle que #23486 vient de standardiser dans `qwen_tts_client.py`, donc un
  seul interrupteur governе le pipeline v4 ;
- resout l'ASR par segment (`_resolve_attack_asr`) au lieu d'un client
  fige a l'import, ce qui rend le cablage testable hors reseau ;
- delegue le verdict a `attack_guard.evaluate_chunk` (l'organe canonique)
  au lieu d'un `detect_missing_attack` re-invoque ;
- porte 9 tests hermetiques du cablage (`test_p5_cv3_attack_guard.py`).

Les deux helpers de main (`_cv3_chunk_asr`, `_cv3_chunk_attack_dropped`)
sont supprimes : ils ne servaient plus qu'ici et n'etaient references par
aucun autre module. Les 3 tests de main qui les monkeypatchaient sont
adapteres a la convention `V4_ATTACK_GUARD`.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@jsboige

jsboige commented Oct 30, 2026

Copy link
Copy Markdown
Owner Author

Conflit de merge résolu — la PR repasse de CONFLICTING à MERGEABLE, tête 8103de36fd.

main a livré deux fois la garde d'attaque depuis l'ouverture de cette branche : #22569 (b63fbd7ba9, chemin CV3, off par défaut via NARRATOR_CV3_ATTACK_GUARD) puis #23486 (b20529e074, chemins narrateur Qwen, convention V4_ATTACK_GUARD). Le conflit de contenu sur v4/p5_tts.py portait donc sur deux implémentations concurrentes du même comportement dans la même fonction.

Résolution : version de cette branche conservée, pour trois raisons mesurées (détail dans le body) — (1) elle lit la convention V4_ATTACK_GUARD que #23486 vient de standardiser, donc un seul interrupteur gouverne le pipeline v4 au lieu de deux variables concurrentes ; (2) son verdict est délégué à attack_guard.evaluate_chunk, l'organe canonique, au lieu d'un detect_missing_attack ré-invoqué qui perdait le compte des mots perdus ; (3) l'ASR est résolu par segment (callable injectable) au lieu d'un client figé à l'import, ce qui rend le câblage testable hors réseau et hors GPU.

Les deux helpers de main (_cv3_chunk_asr, _cv3_chunk_attack_dropped) sont supprimés : ils ne servaient plus qu'à la boucle CV3 et n'étaient référencés par aucun autre module. Les 3 tests de #22569 qui les monkeypatchaient sont adaptés à V4_ATTACK_GUARD ; le stub de verdict propre de _install_fake_runtime est devenu un défaut désactivable (guard_stub=False), sans quoi il répondait à la place du test et l'onset-drop n'était jamais observé (échec réel mesuré : assert 1 == 2 sur out.attempts).

Mesures : pytest v4/tests/ -k "p5 or attack_guard or p7_omission or qwen_chunk" → 118 passed, 0 échec (34 tests CV3 : 25 narrateur + 9 garde). H.3 trivial : aucune cellule de notebook touchée, 0 cellule sans execution_count sur les deux carnets de la série fusionnés. git merge-tree --write-tree entre la tête de #23492 et la mienne rend rc=0, arbre propre — fonctions disjointes dans p5_tts.py. Plus aucune PR ouverte ne touche p5_tts.py.

Le commit de résolution est 8103de36fd (message : « Merge origin/main into feature/audio-attack-guard-inline -- le conflit p5_tts.py est resolu par supersession »).

@jsboige
jsboige merged commit 26f4063 into main Oct 31, 2026
27 of 33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

large-pr-no-review PR > seuil sans review (ni bot ni humaine) -- retire quand une review arrive (#11232) pr-overlap Advisory: another open PR touches the same files (organ #13615)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant