十七次失败的运行与一条平坦的 sigmoid:我们如何构建 supA 门控

zn21 分钟阅读

在上一篇文章中,我们讲述了训练语料库如何悄悄误导了我们,以及我们为此做了什么。本文是故事的后半部分,也没那么光彩:即使数据集修好之后,我们的模型依然什么也检测不出来。几个月的训练运行都没能通过我们自己的发布门控。当我们终于把系统正确地埋点监测之后,分数依然平坦地停在抛硬币的水平。本文将梳理哪里出了问题、修复方法究竟是什么,以及修复后的门控目前交出的数字,包括我们并不引以为傲的部分。

摘要

  • 在 v9 到 v11 的修订中,我们在一次扫描中运行了 17 组训练配置。没有一组通过我们的发布门控。 表现最好的漏报率介于 63% 与 98% 之间。
  • 尽管如此,我们仍然发布了重建后的数据集,并附上了证据:当时表现最好的模型在其上的 FNR 为 86%。
  • 调试之夜发现,数百条输入的分数都被钉在 0.498 至 0.509 之间。根本原因:我们的训练目标是对随机同标签样本对的一种检索式损失。标签从未进入梯度。编码器学会的是主题聚类,而从未学会攻击与良性样本之间的间隔;分类器头则建立在为错误任务塑造的特征之上。
  • 修复方法是单阶段的端到端监督训练:对编码器与分类头联合使用 BCE,AdamW 学习率分别为 2e-5 和 1e-3,预热占 10%,训练 6 个 epoch,并且仅从训练划分中对良性的"长得像攻击"样本进行 x4 过采样。
  • 在冻结的测试划分上结果为:FPR 0.82%,FNR 8.20%,AUROC 0.9958,CPU 上 int8 量化后的 p50 延迟为 17.9 毫秒。使用的正是我们差点放弃的同一个 MiniLM-L6 编码器。
  • 该门控今天以 supA 之名上线于 POST https://api.usezn.com/v30/analyze:这是为第二个使用场景设立的第二个端点,而非替代品。/prod/analyze 仍然是我们的规则门控,也是默认分发的版本:适用于内联热路径门控的超低延迟路径。v30 以毫秒换取显著更好的检测效果。下文有并排的诚实对比。

十七次运行,零次通过

在 v13d 的停止规则触发并重建语料库之后,我们把新数据接入了现有的训练流水线。结果并不理想。v9 到 v11 的修订积累了大量失败的配置,最终是一次 17 组配置的扫描,其中每一组都未能通过我们预先登记的发布门控。根据配置不同,最佳漏报率在 63% 到 98% 之间。一个漏掉大多数攻击的安全门控等于摆设,因此没有任何一组配置被交付。

对此有两种解读。一种是:新数据集太难了,退回到更友好的数据。另一种是:数据集终于说出了一个从未正常工作的模型的真相。我们事先承诺会发布任何结果,包括难看的部分,所以我们选择了第二种。

尽管如此,仍然发布数据集

我们发布了重建后的语料库——23,699 条文本,以 CC-BY-4.0 许可证在 Hugging Face 上公开——而当时表现最好的模型在它上面彻底失败:86% 的攻击都漏过了。这个数字有意保留在数据集文章中。一个你最好的模型无法通过的基准,要么是有缺陷的基准,要么是诚实的基准,而两者的区别要靠修复模型来判定,而不是修改基准。

分数纹丝不动的那个夜晚

接着,我们迎来了将怀疑变为诊断的那个夜晚。我们把当时表现最好的检查点接入探测工具,并输入了数百条内容:攻击、良性提示、改写、多语言攻击。概率输出几乎没有变化。每一个分数都落在 0.498 与 0.509 之间——一次带着装饰性小数位的抛硬币。输入空间中的任何东西似乎都不起作用。

这种平坦本身其实很有信息量。它意味着 logit 对所有输入都徘徊在零附近:模型没有任何可用的排序能力,而调整阈值只不过是在甲板上挪动躺椅。于是我们停止调参,转而追查原因。

两个发现,一个令人尴尬,一个致命:

  1. 我们的基线比较并不公平。 此前的仅规则对比是在一组精心挑选的 217 条提示夹具上运行的。那些失败发生在完整的测试集上——那是一个完全不同的分布。两次测量各自都是真实的;把它们放在一起,却制造了一种并不被任何一方支持的安全感。
  2. 即使数据完美,训练目标也不可能学会检测。 这才是真正的 bug。

一个无视标签的损失函数

我们的训练目标带有检索的色彩:从语料库中抽取样本对,并拉近它们的嵌入。简化后的形式如下:

# What we trained for months (simplified)
for a, b in sampled_pairs(batch):        # pairs drawn from the pool
    za, zb = encode(a), encode(b)
    loss = pull_together(za, zb)         # geometry-only objective

再读一遍这个循环,注意缺少了什么:标签在任何地方都没有出现。一段文本是攻击还是良性提示,对梯度毫无影响。这样的目标教会编码器的是主题聚类:看起来像工具调用的文本会向其他看起来像工具调用的文本靠拢。这对检索来说是个好性质,而对检测几乎没用。没有任何东西在推动攻击远离良性文本,因此几何结构中根本无法形成攻击与良性之间的间隔。

分类器头随后被期望在这个冻结的嵌入空间中画出一条决策边界。要求一个线性层去寻找编码器从未被优化去产生的间隔,结果就是一个对整个宇宙都输出 0.5 的 sigmoid。

检测任务真正需要的,是能够触达编码器的监督信号:

# What detection needs (simplified)
logits = head(encode(text))              # gradients flow into the encoder
loss = binary_cross_entropy(logits, y)   # the label IS the signal

编码器架构相同,数据相同,目标不同——这一次,损失函数可以知道哪些样本是攻击。

修复方法

端到端生效的配方如下:

  • 单阶段监督训练。 对分类头使用二元交叉熵,并同时反向传播到分类头和编码器中。没有冻结的阶段,也没有两阶段的交接。
  • AdamW,两组参数: 编码器学习率 2e-5,分类头学习率 1e-3。
  • 前 10% 步数进行预热,共训练 6 个 epoch。
  • 困难负样本 x4 过采样: 良性的工具调用"相似样本"——那些最应该被误认为攻击的输入——仅从训练划分中挖掘并加权。挖掘器只触碰训练行的 id;按组划分的数据切分(16,888 / 3,301 / 3,510)保证了验证集与测试集不会发生泄漏。
  • 编码器不变。 就是那个我们曾为之写好悼词的、以英语为中心的 MiniLM-L6。

数字

以下所有数字都是在公开语料库的冻结测试划分上测得的:

supA 注入门控在冻结测试划分上的结果 在公开语料库冻结测试划分上测得的指标。误报率:0.82%。漏报率:8.20%。AUROC:0.9958。8 个语言区域的多语言分语言 AUROC:0.90-1.00。CPU 上 int8 ONNX 量化、单流的 p50 延迟:17.9 毫秒。 supA 门控 · 冻结测试划分 误报率 0.82% 漏报率 8.20% 0% 100% AUROC 0.9958 0.50 AUROC 坐标轴 0.50 – 1.00 1.00 多语言分语言 AUROC(8 个语言区域) 0.90-1.00 在同一 0.50 – 1.00 坐标轴上显示的区间 p50 延迟(int8 ONNX,CPU,单流) 17.9 ms

有两行值得评论。多语言一行:修复之前,非英语的准确率崩溃。修复之后,8 个语言区域的分语言 AUROC 都位于 0.90 至 1.00 之间——这证明多语言的崩溃是由损失函数造成的,而不是嵌入编码器的问题。延迟一行:p50 17.9 毫秒是在 CPU 推理上运行的 int8 量化 ONNX,以单流方式测量;生产环境的并发数字会有所不同,我们会在对方法论有信心之后再公布。

诚实的对比:规则 vs. v30

数字摆在桌面上之后,有必要明确说明这套东西如何交付,因为 zn 现在有两个分析端点,它们是为两种不同的任务而构建的。二者都不替代对方。

  • POST /prod/analyze——规则门控。 确定性的模式匹配,请求路径中没有模型:这是我们交付的延迟最低的选项,也是在延迟预算紧张时对每次调用进行内联门控的正确默认值。它以几乎为零的成本可靠地捕获已知模式。
  • POST /v30/analyze——神经门控(supA)。 本文讨论的模型:检测效果显著更好,但延迟明显更高。

两者都在同一个冻结测试划分(3,510 行)上测量——这次没有挑选精心策划的夹具:

同一冻结测试划分上规则门控与神经 supA 门控的对比 规则门控(/prod/analyze)与神经 supA 门控(/v30/analyze)的对比,两者均在同一冻结测试划分上测量。误报率:规则约 8–9%*,神经 supA 0.82%。漏报率:规则约 86–95%*,神经 supA 8.20%。AUROC:规则不适用(确定性),神经 supA 0.9958。推理开销:规则无(无模型调用),神经 supA p50 17.9 毫秒 · int8 ONNX · CPU。 规则 对比 神经 supA · 同一冻结测试划分 规则 · /prod/analyze 神经 SUPA · /v30/analyze 误报率 规则 ~8–9%* SUPA 0.82% 0% 100% 漏报率 规则 ~86–95%* SUPA 8.20% 0% 100% AUROC 规则 不适用(确定性) SUPA 0.9958 0.50 AUROC 坐标轴 0.50 – 1.00 1.00 推理开销 规则 无(无模型调用) SUPA p50 17.9 毫秒 · int8 ONNX · CPU

* 仅规则在测试划分上测得 FPR 8.39% / FNR 94.74%,在全部 23,699 行语料库上测得 FPR 9.60% / FNR 86.43%。两组数据均为公开,因此我们给出区间,而不是挑选对我们最有利的那一端。

直白地说:你以毫秒换取捕获规则完全漏掉的攻击——在这个测试集上,大约每十个中漏掉九个。这并不是对规则引擎的否定。它仍然是我们默认分发的版本,以几乎为零的成本完成它的工作;而一个标记已知模式的确定性层,正是你希望在每一个热路径请求上都耗费零推理时间的东西。

实践建议:当延迟预算紧张时,在每次调用上保留内联的规则门控;对于值得更严格审视的输入(风险较高的提示、抽样流量,或异步与取证评分),则路由到 v30。如果你的威胁模型要求最高召回率,也可以接受处处增加延迟。

这并未解决的问题

三条限制,明确说明:

  1. 测试划分与训练语料库同源。 两者都源自相同的家族蓝图。这是标准做法,划分也无泄漏,但这意味着这些数字衡量的是对已知形态的性能。零日式攻击——真正新颖的家族——会更难应对,我们宁可直说,也不愿让 AUROC 替它们发声。
  2. 8.20% 的 FNR 并不为零。 在我们的工作点上,大约每十二次攻击仍有一次漏过。任何部署该门控的人都应当将其分层使用,而不是只依赖它。
  3. 编码器小且以英语为中心。 多语言数字不错,但并非神奇;资源匮乏的语言区域位于 0.90–1.00 区间的底部。下一阶段计划是将一个教师模型蒸馏到更强的多语言编码器中。

试一试

规则门控仍在其常用地址提供服务;supA 从今天起与之并存:

  • API: POST https://api.usezn.com/prod/analyze(规则——内联、低延迟的默认选项)以及 POST https://api.usezn.com/v30/analyze(神经 supA——更深入的检查)。请求与响应格式相同,使用同一个 API 密钥;按上述建议进行路由。集成文档位于 /docs,完整规范位于 /openapi.json。
  • 代码: 训练器、评估工具和门控位于 github.com/usezn/zn,采用 MIT 许可证。
  • 数据: Hugging Face 上的 zn-prompt-injection-bench,23,699 行,CC-BY-4.0,划分与审计记录在数据集卡片中。

如果你以红队为业,上一篇文章的约定依然有效:攻破它并告诉我们怎么做。让下一个版本变得更好的最快方式,就是找出这一版哪里做错了。

Share