Ce qui se cache sous la tête : quatre leviers d'entraînement, trois graines qui passent et la seq_len qui a fait tout le travail
Il y a deux articles, nous avons reconstruit un corpus qui nous mentait. Il y a un article, nous vous racontions les dix-sept exécutions d'entraînement ratées avant que quoi que ce soit ne fonctionne. Voici le troisième chapitre de notre façon de nous tromper publiquement, avec les chiffres à l'appui.
En bref : notre porte de production, supA-v3, a franchi cinq des six portes de publication préenregistrées du premier coup. La sixième — les faux négatifs par langue — échouait uniquement parce que des attaques en russe passaient entre les mailles. Nous aurions pu la livrer quand même ; rien dans le contrat de publication n'exige une répartition parfaite entre les langues. Mais une porte qui favorise discrètement l'anglais représente un risque pour quiconque déploie des agents hors des forums anglophones. Nous avons donc mené une campagne d'entraînement ciblée pour comprendre pourquoi cela se produisait.
La réponse s'est révélée embarrassante par sa simplicité mécanique. Ce qui suit présente tout ce que nous avons appris, chaque chiffre observé, et une remarque un peu inconfortable sur la facilité avec laquelle cette correction particulière aurait pu passer inaperçue.
En bref
- v3 a franchi le FPR (0,64 % contre un plafond ≤ 1 %), mais a laissé passer 9,11 % des attaques en russe, dépassant de 1,1 point de pourcentage notre budget de FNR par langue.
- Notre premier réflexe — suréchantillonner les attaques en russe dans la perte à un poids effectif x5 — a rendu les choses pires. Le FNR RU est passé de 9,11 % à 31,72 %. Donner plus de poids a déplacé les scores sans améliorer le classement.
- L'AUROC est resté coincé entre 0,99957 et 0,99989 sur les dix exécutions, ce qui suggère que nous ne corrigions pas le classement. Nous redistribuions les points de fonctionnement.
- Le véritable gagnant s'est révélé être
seq_len=96, et non un levier de pondération ou un taux d'apprentissage. C'était le seul changement qui élargissait suffisamment ce que l'encodeur pouvait voir pour remarquer que les motifs d'injection multilingues étaient plus longs que la fenêtre d'encodage. - Trois réentraînements indépendants avec d'autres graines ont confirmé la stabilité : les seuils ont varié (comme le veut la calibration), mais les verdicts des portes se sont reproduits à l'identique.
- Dix exécutions d'entraînement au total pour la campagne. L'expérience la moins coûteuse nous a appris davantage que tout le reste de ce que nous avons fait durant le trimestre.
- En service sur
POST /v30/analyze— voir la documentation développeur pour l'authentification, le schéma des requêtes et des exemples d'appels.
Là où nous en étions
Le corpus est public sur Hugging Face, sous CC-BY-4.0, chaque ligne portant sa provenance. Le pipeline d'entraînement et les portes de publication sont préenregistrés sous forme de JSON lisible par machine avant qu'un modèle ne touche aux données. Chaque exécution d'entraînement est automatiquement consignée avec sa configuration, l'empreinte de son artefact et le résultat de son évaluation, de sorte que la campagne se lit comme une série auditable plutôt que comme un montage de moments forts.
Pour la campagne v3, nous avons entraîné sur 10 554 lignes : 9 754 reprises de v3 plus 800 nouvelles questions bénignes en russe couvrant des sujets de loisirs qu'aucun élément de la partition de validation n'abordait. Les contrôles de contamination se sont révélés propres. Le seuil calibré a donné un FP proche de zéro. Un problème est apparu immédiatement : les taux de faux négatifs par langue racontaient une histoire peu flatteuse.
Cinq langues ont été pratiquement gratuites. La sixième ne l'a pas été.
Première tentative : forcer davantage
L'intuition évidente, lorsqu'un segment précis échoue au rappel, consiste à insister davantage sur ce segment pendant l'entraînement. Pondérer plus fortement la perte pour ce que l'on veut détecter plus souvent. Si le x3 sur les cas limites faisait déjà partie de la recette de base, peut-être que les attaques en russe avaient besoin d'un x15 au total.
L'exécution a fonctionné mécaniquement : l'entraînement s'est terminé, l'artefact est sorti, la contamination était propre. Puis l'évaluation est arrivée et nous a laissés silencieux une seconde :
Le FNR RU est passé de 9,11 % à 31,72 %.
L'AUROC a à peine bougé (0,99957 → 0,99969). Cet écart compte. Un modèle dont le classement ne s'améliore pas ne peut pas être réparé en modifiant les poids de la perte : on ne fait que déplacer le seuil le long d'une courbe fixe. C'est exactement ce qui s'est passé : le seuil calibré a été poussé jusqu'à 0,99, ce qui signifie que presque chaque décision est devenue beaucoup plus conservatrice, et l'effondrement du rappel a suivi mécaniquement.
Le balayage des seuils a montré le véritable tableau. À chaque point de la grille où le FPR descendait sous notre plafond de 1 %, le FNR RU se situait au-dessus de la porte de 8 %, et inversement. Les deux contraintes ne se croisent pas pour cette configuration. Aucune chasse aux seuils ne trouvera une valeur qui n'existe pas.
Deuxième tentative : abandonner les poids
Si pousser le poids de la perte ne marche pas, l'hypothèse raisonnable suivante est de nature architecturale. En particulier, le russe se tokenisait en davantage de tokens que les cinq autres langues avec le tokeniseur de MiniLM — environ 30 à 40 % de tokens en plus en moyenne pour un contenu sémantique équivalent. Si les séquences étaient tronquées à 64 tokens, la fin d'une attaque pourrait être invisible.
Nous avons testé deux variantes :
- seq_len = 96, tout le reste par défaut
- seq_len = 128 + lr_head porté à 2e-3
Les deux ont réussi. Proprement. Toutes les portes. Dans l'ordre.
Ce schéma — les deux expériences de longueur de séquence qui réussissent quels que soient les réglages du taux d'apprentissage, alors que toutes les variantes ne touchant pas à la longueur de séquence continuaient d'échouer — est exactement ce à quoi ressemble une histoire de fenêtre de tokenisation lorsqu'elle apparaît dans vos données.
Quatre configurations indépendantes ont atteint TOUTES LES PORTES RÉUSSIES :
Trois autres candidats ont échoué à une ou plusieurs portes, surtout des variantes à séquence courte où un taux d'apprentissage plus élevé restait insuffisant.
La partie droite de ce graphique est volontairement monotone. Les deux configurations réussies se trouvent dans le même voisinage, qu'on ait doublé ou non le taux d'apprentissage. La longueur de séquence fait le gros du travail.
Vérifier que ce n'était pas de la chance
Quatre réussites dans un balayage ne prouvent pas une causalité. Pour vérifier que nous avions affaire à une recette robuste plutôt qu'à une combinaison heureuse d'hyperparamètres, nous avons réentraîné exactement la configuration gagnante deux fois de plus, avec des graines aléatoires différentes. Tout le reste était identique.
Le seuil calibré a bougé (0,50 → 0,91 → 0,80), comme attendu : les distributions de probabilité se déplacent légèrement d'une graine à l'autre. Les verdicts des portes, eux, n'ont pas bougé. Lorsque des initialisations aléatoires différentes produisent des placements de seuil indépendants mais des conclusions identiques sur les portes dures, on tient un résultat et non une anecdote.
En combinant cela avec les balayages de type validation croisée présentés plus haut, nous avons désormais sept configurations distinctes qui passent la suite préenregistrée complète, toutes issues du même choix structurel sous-jacent.
À quoi ressemble réellement l'exécution gagnante
Sur la partition de réserve de régression figée, avec le seuil calibré de l'exécution que nous déployons :
- Taux de faux positifs : 0,64 % mesuré — 1 prompt négatif propre sur 156 a déclenché la porte
- Faux négatifs par langue : RU 0,56 % — IT 0,00 % — PT 0,00 % — ES 0,00 % — DE 0,12 % — EN 0,00 %
- AUROC sur bench-core : 0,99986
- Taux de capture des échecs de PromptGuard : 98,8 %
- Latence d'inférence CPU (int8 ONNX, batch=1) : p50 ≈ 27 ms, p95 ≈ 45 ms
Ce que cela NE résout PAS
Être honnête sur les limites est précisément le but de publier ces chiffres en public.
Nous avons testé la capacité de chaque modèle à détecter des paraphrases de choses que le moteur de règles connaît déjà, ainsi que de véritables attaques synthétiques inédites générées contre cette famille de corpus. Le comportement réel des adversaires évolue plus vite que tout jeu de données hors ligne. Quelque chose finira par contourner à la fois les expressions régulières et le classifieur, d'une manière que nos réserves n'avaient jamais anticipée. L'intérêt de mesurer rigoureusement, c'est de savoir avec quoi l'on travaille, pas de prétendre avoir résolu la détection pour de bon.
Deuxièmement : la configuration gagnante utilise des séquences de 96 tokens au lieu de 64. Le coût d'inférence CPU augmente modérément (environ 45 % plus lent que seq64 sur notre matériel). Il n'y a pas non plus de raison que des longueurs de séquence plus grandes continuent d'aider indéfiniment ; le mode de défaillance traité est précisément la troncature des phrases russes plus longues, et au-delà de cette fenêtre, on paie du calcul pour un bénéfice marginal.
Troisièmement : les exclusions des partitions canari et bench-private-holdout sont tenues par assertion d'identifiants. Cette discipline anti-contamination résiste aux audits, mais reste fragile face à de nouvelles sources de corpus que nous n'avons pas encore ingérées.
Quatrièmement : neuf configurations de cette campagne ont échoué à une ou plusieurs portes avant qu'une seule ne réussisse. Ne publier que des histoires de réussite n'apprend rien sur la façon de construire le pipeline qui les détecte. Chaque échec — y compris les catastrophiques, comme l'effondrement à 31,72 % du poids x5 — est conservé dans le journal des commits exactement tel qu'il s'est déroulé.
Et ensuite
Déploiement fantôme aux côtés du moteur de règles déterministes sur POST /v30/analyze, en mode OU combiné : une correspondance regex ou un score de modèle au-delà du seuil entraîne un blocage ; l'un seul ne mène pas forcément à une autorisation. Le moteur de règles traite ce que la tête manque ; le réseau neuronal capte tout ce que les regex ne voient pas. L'empilement de latence est intentionnel : le chemin regex est peu coûteux et sert de solution de repli si le runtime ONNX se comporte mal.
Envie de l'essayer vous-même ? Rendez-vous sur la documentation développeur : le contrat complet de /v30/analyze, la configuration de l'authentification et le schéma des requêtes y sont documentés, ainsi que /openapi.json une fois les métriques fantômes stabilisées. Les notes d'intégration client paraîtront dans le changelog après la période de rodage en staging.
Mis à jour le 14 octobre 2026 : formulation modifiée pour présenter des résultats mesurés.