O que vive por baixo da cabeça: quatro botões de treino, três sementes que passam e o seq_len que fez todo o trabalho

zn13 min de leitura

Há dois posts, reconstruímos um corpus que nos andava a mentir. Há um post, contámos-lhe as dezassete execuções de treino falhadas antes de algo funcionar. Este é o capítulo três de nós a estarmos errados em público, com números anexados.

A versão curta: a nossa porta de produção, supA-v3, passou cinco das seis portas de lançamento pré-registadas à primeira. A sexta — falsos negativos por língua — falhava exclusivamente porque ataques em russo passavam. Podíamos tê-la lançado na mesma; nada no contrato de lançamento exige uma divisão perfeita entre línguas. Mas uma porta que prefere discretamente o inglês é uma responsabilidade para quem implementa agentes fora dos fóruns anglófonos, por isso corremos uma campanha de treino focada para descobrir porque é que acontecia.

A resposta acabou por ser embaraçosamente mecânica. O que se segue é tudo o que aprendemos, cada número que vimos e uma observação um pouco incómoda sobre como seria fácil ter deixado passar esta correção em particular.

Resumo

  • A v3 passou a FPR (0,64% contra um teto de ≤1%), mas deixou passar 9,11% dos ataques em russo, estourando em 1,1 pontos percentuais o nosso orçamento de FNR por língua.
  • O nosso primeiro instinto — aumentar o peso das amostras de ataque em russo na perda para um peso efetivo de x5 — piorou as coisas. O FNR em RU saltou de 9,11% para 31,72%. Mais peso mexeu nas pontuações sem melhorar o ranking.
  • O AUROC manteve-se entre 0,99957 e 0,99989 nas dez execuções, o que sugere que não estávamos a corrigir o ranking. Estávamos a redistribuir os pontos de operação.
  • O verdadeiro vencedor foi seq_len=96, não qualquer botão de ponderação ou taxa de aprendizagem. Foi a única alteração que expandiu o que o codificador conseguia ver durante tempo suficiente para notar que os padrões de injeção multilingues eram mais longos do que a janela de codificação.
  • Três reexecuções independentes com sementes diferentes confirmaram a estabilidade: os limiares variaram (como a calibração exige que variem), mas os resultados das portas reproduziram-se de forma idêntica.
  • Dez execuções de treino no total na campanha. A experiência mais barata ensinou-nos mais do que tudo o resto que fizemos neste trimestre.
  • Em produção em POST /v30/analyze — veja a documentação para programadores para autenticação, esquema de pedidos e exemplos de chamadas.

Onde ficámos

O corpus está público no Hugging Face, CC-BY-4.0, com a proveniência de cada linha. O pipeline de treino e as portas de lançamento estão pré-registados como JSON legível por máquina antes de qualquer modelo tocar nos dados. Cada execução de treino é registada automaticamente com a sua configuração, o hash do artefacto e o resultado da avaliação, pelo que a campanha se lê como uma série auditável e não como um vídeo de melhores momentos.

Para a campanha v3 treinámos com 10.554 linhas: 9.754 transitadas da v3 mais 800 perguntas benignas novas em russo, abrangendo temas de passatempo que nenhum item da partição de validação tocava. As verificações de contaminação saíram limpas. O limiar calibrado deu um FP praticamente zero. Um problema surgiu logo: as taxas de falsos negativos por língua contavam uma história feia.

supA-v3 · FNR por língua face à porta pré-registada LÍNGUA FNR FACE À PORTA DE 8% EN0,00% PASSA ES0,12% PASSA DE0,12% PASSA PT0,00% PASSA IT0,46% PASSA RU9,11% FALHA A única medição por língua que falha na supA-v3. Tudo o resto passou com uma margem de uma ordem de grandeza.

Cinco línguas saíram praticamente de graça. A sexta não.

Primeira tentativa: carregar mais

A intuição óbvia quando uma fatia específica falha no recall é carregar mais nessa fatia durante o treino. Ponderar mais a perda para o que se quer apanhar com mais frequência. Se o x3 em casos limite já fazia parte da receita base, talvez os ataques em russo precisassem de um x15 no total.

A execução funcionou mecanicamente: o treino terminou, o artefacto apareceu, a contaminação estava limpa. Depois a avaliação chegou e deixou-nos em silêncio durante um segundo:

O FNR em RU passou de 9,11% para 31,72%.

O AUROC mexeu-se pouco (0,99957 → 0,99969). Essa discrepância importa. Um modelo cujo ranking não melhora não pode ser corrigido mudando os pesos da perda: só se está a deslocar o ponto de corte ao longo de uma curva fixa. E foi exatamente isso que aconteceu: o limiar calibrado acabou empurrado para 0,99, o que significa que quase todas as decisões ficaram muito mais conservadoras, e o colapso do recall seguiu-se mecanicamente.

A varredura de limiares mostrou o quadro real. Em cada ponto da grelha onde a FPR descia abaixo do nosso teto de 1%, o FNR em RU ficava acima da porta de 8%, e vice-versa. As duas restrições não se cruzam para essa configuração. Nenhuma quantidade de procura de limiares encontra um valor que não existe.

Segunda tentativa: largar os pesos

Se empurrar o peso da perda não funciona, a hipótese razoável seguinte é arquitetural. Em particular, o russo era tokenizado em mais tokens do que as outras cinco línguas com o tokenizador do MiniLM — cerca de 30 a 40% mais tokens em média para conteúdo semanticamente equivalente. Se as sequências estivessem a ser cortadas aos 64 tokens, a cauda de um ataque poderia ser invisível.

Testámos duas variantes:

  • seq_len = 96, tudo o resto por defeito
  • seq_len = 128 + lr_head subida para 2e-3

Ambas passaram. Limpamente. Todas as portas. Em sequência.

Esse padrão — as duas experiências de comprimento de sequência a passar independentemente dos ajustes de taxa de aprendizagem, enquanto todas as variantes que não mexiam no comprimento continuavam a falhar — é exatamente o que parece uma história de janela de tokenização quando aparece nos seus dados.

Quatro configurações independentes atingiram TODAS AS PORTAS PASSAM:

Quatro configurações que passam TODAS as portas pré-registadas EXECUÇÃO SEQ_LEN LR_HEAD LIMIAR FP /156 PIOR FNR v4cc4 961e-3 0,500 0,56% v4c6 1282e-3 0,500 0,45% v4cc3 642e-3 0,571 1,24% v4c5 965e-4 0,681 1,35% As linhas azuis marcam as configurações com 0 FP neste conjunto (os candidatos a implementação).

Três outros candidatos falharam uma ou mais portas, sobretudo variantes de sequência curta em que uma taxa de aprendizagem mais alta ainda era insuficiente.

FNR da pior língua por comprimento de sequência (partição de teste congelada) 32% 16% 0% porta 8% 31,72 17,55 18,56 16,09 seq_len 64 - todas FALHAM 0,56 1,35 seq_len 96 - passa em todas as portas 0,45 seq128 Quanto mais baixo, melhor. As barras vermelhas falham a porta pré-registada por língua.

O lado direito desse gráfico é aborrecido por desenho. As duas configurações que passam estão na mesma vizinhança, quer tenhamos duplicado a taxa de aprendizagem quer não. O comprimento da sequência é que faz o trabalho pesado.

Confirmar que não foi sorte

Quatro aprovações num varrimento não provam causalidade. Para verificar que estávamos perante uma receita robusta e não uma combinação afortunada de hiperparâmetros, voltámos a treinar exatamente a configuração vencedora duas vezes, com sementes aleatórias diferentes. Tudo o resto igual.

VERIFICAÇÃO DE ESTABILIDADE - reexecuções com sementes independentes da configuração vencedora Semente 20260827 FNR da pior língua 2,81%· FPR 0,64%Passa Semente 20260828 FNR da pior língua 2,14%· FPR 0,64%Passa

O limiar calibrado mexeu-se (0,50 → 0,91 → 0,80), como esperado — as distribuições de probabilidade deslocam-se ligeiramente por semente. Os resultados das portas não se mexeram. Quando inicializações aleatórias diferentes produzem posicionamentos de limiar independentes mas conclusões idênticas nas portas duras, tem-se um achado e não uma anedota.

Combinando isto com os varrimentos de tipo validação cruzada acima, temos agora sete configurações distintas a passar a suíte pré-registada completa, todas remontando à mesma escolha estrutural subjacente.

Como é de facto a execução vencedora

Na partição de retenção de regressão congelada, com o limiar calibrado da execução que implementamos:

  • Taxa de falsos positivos: 0,64% medidos — 1 em cada 156 prompts negativos limpos disparou
  • Falsos negativos por língua: RU 0,56% — IT 0,00% — PT 0,00% — ES 0,00% — DE 0,12% — EN 0,00%
  • AUROC sobre bench-core: 0,99986
  • Taxa de captura nas falhas do PromptGuard: 98,8%
  • Latência de inferência em CPU (int8 ONNX, batch=1): p50 ≈ 27 ms, p95 ≈ 45 ms
supA-v4cc4 - retenção de regressão congelada - números finais Taxa de falsos positivos 0,64% FNR da pior língua (russo) 0,56% FNR global 0,15% Inferência p50 em CPU (int8 ONNX) 27 ms

O que isto NÃO resolve

Ser honesto sobre os limites é precisamente o objetivo de publicar estes números em público.

Testámos a capacidade de cada modelo para apanhar parafrasear coisas que o motor de regras já conhece, mais ataques sintéticos genuinamente novos gerados contra esta família de corpus. O comportamento real do adversário muda mais depressa do que qualquer dataset offline consegue acompanhar. Algo acabará por contornar tanto as regex como o classificador de formas que as nossas retenções nunca anteciparam. O objetivo de medir com rigor é saber com o que se está a lidar, não fingir que a deteção ficou resolvida para sempre.

Segundo: a configuração vencedora usa sequências de 96 tokens em vez de 64. O custo de inferência em CPU sobe moderadamente (cerca de 45% mais lento do que seq64 no nosso hardware). Também não há razão para que comprimentos de sequência maiores continuem a ajudar indefinidamente; o modo de falha em causa é especificamente o corte de frases russas mais longas, e para além dessa janela está a pagar computação por um benefício marginal.

Terceiro: as exclusões das partições canário e bench-private-holdout são mantidas por asserção de IDs. Essa disciplina de contaminação aguenta auditorias, mas é frágil face a fontes de corpus novas que ainda não ingerimos.

Quarto: nove configurações desta campanha falharam uma ou mais portas antes de alguma ter sucesso. Publicar apenas histórias de aprovação não ensina nada sobre como construir o pipeline que as apanha. Cada falha — incluindo as catastróficas, como o colapso de x5 no peso para 31,72% — fica preservada no registo de commits exatamente como correu.

O que se segue

Implementação em modo sombra junto do motor de regras determinista em POST /v30/analyze, em modo OR combinado: uma correspondência de regex ou uma pontuação do modelo acima do limiar resulta em bloqueio; cada uma por si não resulta necessariamente em permissão. O motor de regras trata do que a cabeça deixar passar; a rede neuronal apanha tudo o que as regex não conseguem ver. O empilhamento de latência é intencional: o caminho de regex é leve e funciona como recurso se o runtime ONNX se portar mal.

Quer experimentar? Vá a a documentação para programadores: o contrato completo de /v30/analyze, a configuração de autenticação e o esquema de pedidos estão lá documentados, juntamente com /openapi.json quando as métricas em sombra estabilizarem. As notas de integração de cliente chegarão ao changelog depois do período de teste em staging.

Atualizado a 14 de outubro de 2026: redação alterada para apresentar resultados medidos.

Share