Text Classification
PEFT
Safetensors
English
safety
guardrail
constitutional-classifiers
jailbreak-detection
content-moderation
Instructions to use EuroSafeAI/EuroSafeGuard with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use EuroSafeAI/EuroSafeGuard with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Add full mode (paper's end-to-end configuration) and its per-seed thresholds; fix model card; remove __pycache__
Browse files- README.md +52 -16
- __pycache__/eurosafeguard.cpython-312.pyc +0 -0
- eurosafeguard.py +66 -2
- thresholds/full_seed0.json +18 -0
- thresholds/full_seed1.json +18 -0
- thresholds/full_seed2.json +18 -0
- thresholds/full_seed3.json +18 -0
- thresholds/full_seed4.json +18 -0
README.md
CHANGED
|
@@ -21,9 +21,8 @@ pipeline_tag: text-classification
|
|
| 21 |
language model and the replies the model produces, and it is trained on data generated from a written constitution of
|
| 22 |
eleven harm categories.
|
| 23 |
|
| 24 |
-
Paper: *
|
| 25 |
-
|
| 26 |
-
[EuroSafeGuard-replies](https://huggingface.co/datasets/EuroSafeAI/EuroSafeGuard-replies) (gated)
|
| 27 |
|
| 28 |
## Components
|
| 29 |
|
|
@@ -32,8 +31,9 @@ Paper: *EuroSafeAI Open Constitutional Classifiers* (arXiv: TODO) · Code: TODO
|
|
| 32 |
| `probe/` | MLP probe (hidden size 256) on the activations of all 32 blocks of the protected model, reading the chat-formatted prompt; per-token logits are smoothed with an exponential moving average and the prompt score is their maximum | Llama-3.1-8B-Instruct |
|
| 33 |
| `gate/seed{0..4}/` | Input classifier: LoRA adapter + one sigmoid head per harm category (11 heads); score = highest head | Qwen3.5-9B |
|
| 34 |
| `exchange/` | Exchange classifier: reads `User: <prompt>\n\nAssistant: <reply>`; LoRA + 11 heads | Qwen3.5-9B |
|
| 35 |
-
| `thresholds/seed{k}.json` |
|
| 36 |
-
| `
|
|
|
|
| 37 |
|
| 38 |
The five `gate/seed*` folders are the five training runs (same data, different seeds) that the paper reports as a mean.
|
| 39 |
They vary, especially against search attacks such as PAIR, so evaluate the seed you deploy.
|
|
@@ -42,7 +42,14 @@ They vary, especially against search attacks such as PAIR, so evaluate the seed
|
|
| 42 |
Integrity, Physical Harm, Privacy, Self-Harm, Sexual Content. `taxonomy_11heads_short.json` lists what each category
|
| 43 |
allows and disallows.
|
| 44 |
|
| 45 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
1. **Input stage.** The input score is the mean of the probe and input-classifier scores, each z-scored on
|
| 48 |
WildChat-clean. Its threshold (`alert_input_thr`) flags 5% of WildChat-clean prompts.
|
|
@@ -64,6 +71,28 @@ if decision == "refuse" or g.check_reply(prompt, reply, decision):
|
|
| 64 |
reply = "Sorry, I can't help with that."
|
| 65 |
```
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
**Requirements:** `torch`, `transformers` with Qwen3.5 support, `peft`, `safetensors`, `huggingface_hub`. About 16 GB of
|
| 68 |
GPU memory for Llama-3.1-8B (the probe) plus about 19 GB for each Qwen3.5-9B classifier (input and exchange). Use
|
| 69 |
`load_exchange=False` to run the input stage only. The probe reads Llama-3.1-8B-Instruct activations. If you protect
|
|
@@ -71,13 +100,18 @@ Llama-3.1-8B-Instruct itself, you can reuse the activations of its forward pass.
|
|
| 71 |
|
| 72 |
## Results (from the paper; every guard calibrated to the same false-positive budget on real chat traffic)
|
| 73 |
|
| 74 |
-
- **
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
- **PAIR** (iterative black-box attack), input stage at the 5% WildChat-clean budget: 21% of the behaviors that break
|
| 77 |
the unprotected model get through, against 49% for the best other open guard.
|
| 78 |
-
- **Chemical and biological goals** transferred to three
|
| 79 |
-
- **Alert mode** (
|
| 80 |
-
from 37% to 5–
|
| 81 |
- **Latency:** the full guardrail adds 1.5% to generation time on the requests it escalates.
|
| 82 |
|
| 83 |
Numbers are means over the five input-classifier seeds; see the paper for the full tables, judges and budgets.
|
|
@@ -100,15 +134,17 @@ Numbers are means over the five input-classifier seeds; see the paper for the fu
|
|
| 100 |
Our adapters, heads, probe and code are released under Apache-2.0. The base models keep their own licenses:
|
| 101 |
[Llama 3.1 Community License](https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct) for the probe's protected model,
|
| 102 |
and the [Qwen license](https://huggingface.co/Qwen/Qwen3.5-9B) for the classifier backbones. Some training data is
|
| 103 |
-
licensed for non-commercial use only
|
| 104 |
|
| 105 |
## Citation
|
| 106 |
|
| 107 |
```bibtex
|
| 108 |
-
@article{
|
| 109 |
-
title = {
|
| 110 |
-
author = {Waibl, Leonhard and Chen, Yen-Shan and Simko, Samuel and
|
| 111 |
-
|
|
|
|
|
|
|
| 112 |
year = {2026}
|
| 113 |
}
|
| 114 |
```
|
|
|
|
| 21 |
language model and the replies the model produces, and it is trained on data generated from a written constitution of
|
| 22 |
eleven harm categories.
|
| 23 |
|
| 24 |
+
Paper: *EuroSafeGuard: Open Constitutional Classifiers* (arXiv: coming soon) · Code: coming soon ·
|
| 25 |
+
Training prompts: coming soon (gated access) · Project page: [guard.safe.eu](https://guard.safe.eu)
|
|
|
|
| 26 |
|
| 27 |
## Components
|
| 28 |
|
|
|
|
| 31 |
| `probe/` | MLP probe (hidden size 256) on the activations of all 32 blocks of the protected model, reading the chat-formatted prompt; per-token logits are smoothed with an exponential moving average and the prompt score is their maximum | Llama-3.1-8B-Instruct |
|
| 32 |
| `gate/seed{0..4}/` | Input classifier: LoRA adapter + one sigmoid head per harm category (11 heads); score = highest head | Qwen3.5-9B |
|
| 33 |
| `exchange/` | Exchange classifier: reads `User: <prompt>\n\nAssistant: <reply>`; LoRA + 11 heads | Qwen3.5-9B |
|
| 34 |
+
| `thresholds/seed{k}.json` | Alert mode: score normalisation and thresholds for each input-classifier seed | |
|
| 35 |
+
| `thresholds/full_seed{k}.json` | Full mode: normalisation (probe, gate, exchange), probe escalation cut, weights and threshold for each seed | |
|
| 36 |
+
| `eurosafeguard.py` | Single-file loader with both modes | |
|
| 37 |
|
| 38 |
The five `gate/seed*` folders are the five training runs (same data, different seeds) that the paper reports as a mean.
|
| 39 |
They vary, especially against search attacks such as PAIR, so evaluate the seed you deploy.
|
|
|
|
| 42 |
Integrity, Physical Harm, Privacy, Self-Harm, Sexual Content. `taxonomy_11heads_short.json` lists what each category
|
| 43 |
allows and disallows.
|
| 44 |
|
| 45 |
+
## Two modes
|
| 46 |
+
|
| 47 |
+
| Mode | Load with | Paper configuration | Paper tables |
|
| 48 |
+
|---|---|---|---|
|
| 49 |
+
| `alert` (default) | `mode="alert"` | EuroSafeGuard (Input) at the 5% WildChat-clean budget, plus alert-unless-CBRN and the exchange check | Held-out attacks (Input columns), alert mode ("Alert, strict exchange") |
|
| 50 |
+
| `full` | `mode="full"` | EuroSafeGuard (Full): probe escalation, weighted sum of probe, input and exchange scores | End-to-end table |
|
| 51 |
+
|
| 52 |
+
### Alert mode (input/alert, alert-unless-CBRN)
|
| 53 |
|
| 54 |
1. **Input stage.** The input score is the mean of the probe and input-classifier scores, each z-scored on
|
| 55 |
WildChat-clean. Its threshold (`alert_input_thr`) flags 5% of WildChat-clean prompts.
|
|
|
|
| 71 |
reply = "Sorry, I can't help with that."
|
| 72 |
```
|
| 73 |
|
| 74 |
+
### Full mode (the paper's end-to-end configuration)
|
| 75 |
+
|
| 76 |
+
1. **Probe screen.** The probe scores every request. Only requests whose probe z-score exceeds `probe_escalation_z`
|
| 77 |
+
(1.1644, the 90th percentile on WildChat-clean) are escalated; the rest are never blocked.
|
| 78 |
+
2. **Escalated requests** are scored by the input classifier and, once the protected model has replied, by the exchange
|
| 79 |
+
classifier. The exchange is blocked if `0.6·probe_z + 0.2·input_z + 0.2·exchange_z > full_thr`.
|
| 80 |
+
3. All three scores are z-scored on WildChat-clean (the exchange on Llama-3-8B-Instruct replies: μ = 0.02560,
|
| 81 |
+
σ = 0.09032). `full_thr` is the smallest threshold that keeps every benign calibration set under its cap (WildChat-clean,
|
| 82 |
+
MMLU, GSM8K, Alpaca, Dolly, OASST 0.5%; XSTest-safe, OR-Bench-80k 2%; OR-Bench-hard 5%; benign rows of styled
|
| 83 |
+
jailbreak sets 40%): 3.382 / 3.571 / 3.132 / 3.097 / 3.318 for seeds 0–4. It was fitted once on the undefended
|
| 84 |
+
Llama-3-8B-Instruct's traffic.
|
| 85 |
+
|
| 86 |
+
```python
|
| 87 |
+
g = EuroSafeGuard.from_pretrained("EuroSafeAI/EuroSafeGuard", seed=0, mode="full", devices=("cuda:0", "cuda:0", "cuda:1"))
|
| 88 |
+
reply = ... # your protected model
|
| 89 |
+
if g.check_full(prompt, reply): # probe on every request; gate + exchange only if escalated
|
| 90 |
+
reply = "Sorry, I can't help with that."
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
`full_block(probe, gate, exchange, cfg)` applies the same rule to precomputed raw scores. On the paper's stored scores it
|
| 94 |
+
reproduces the end-to-end attack success of every seed exactly (mean 3.88%).
|
| 95 |
+
|
| 96 |
**Requirements:** `torch`, `transformers` with Qwen3.5 support, `peft`, `safetensors`, `huggingface_hub`. About 16 GB of
|
| 97 |
GPU memory for Llama-3.1-8B (the probe) plus about 19 GB for each Qwen3.5-9B classifier (input and exchange). Use
|
| 98 |
`load_exchange=False` to run the input stage only. The probe reads Llama-3.1-8B-Instruct activations. If you protect
|
|
|
|
| 100 |
|
| 101 |
## Results (from the paper; every guard calibrated to the same false-positive budget on real chat traffic)
|
| 102 |
|
| 103 |
+
- **End to end** (Full mode, Llama-3-8B-Instruct, 1,974 Jailbreak Foundry attacks on held-out GuidedBench behaviors):
|
| 104 |
+
attack success falls from 7.95% to 3.88% (EuroSafe-CC++, our reimplementation of Constitutional Classifiers++, 4.57%),
|
| 105 |
+
adding at most 3.2 points of benign failure on any of six benign benchmarks. On gpt-oss-20b: 13.17% → 5.28%.
|
| 106 |
+
- **Compute:** only the 10% of requests the probe escalates reach the two classifiers, so the guardrail adds 15% of the
|
| 107 |
+
protected model's FLOPs.
|
| 108 |
+
- **Held-out jailbreaks**, input stage at the 5% WildChat-clean budget: 95.9% recall on the BELLS-O jailbreak set
|
| 109 |
+
(88.2% for HaloGuard, the strongest open guard); highest recall on three of four held-out jailbreak sets.
|
| 110 |
- **PAIR** (iterative black-box attack), input stage at the 5% WildChat-clean budget: 21% of the behaviors that break
|
| 111 |
the unprotected model get through, against 49% for the best other open guard.
|
| 112 |
+
- **Chemical and biological goals** transferred to three protected models: the full guardrail lets through at most 1.4% of goals on any model.
|
| 113 |
+
- **Alert mode** (`mode="alert"`) vs refusing every flagged request: refusals of benign sensitive requests fall
|
| 114 |
+
from 37% to 5–16% on six protected models, while at most 3.6% of chemical and biological goals get through.
|
| 115 |
- **Latency:** the full guardrail adds 1.5% to generation time on the requests it escalates.
|
| 116 |
|
| 117 |
Numbers are means over the five input-classifier seeds; see the paper for the full tables, judges and budgets.
|
|
|
|
| 134 |
Our adapters, heads, probe and code are released under Apache-2.0. The base models keep their own licenses:
|
| 135 |
[Llama 3.1 Community License](https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct) for the probe's protected model,
|
| 136 |
and the [Qwen license](https://huggingface.co/Qwen/Qwen3.5-9B) for the classifier backbones. Some training data is
|
| 137 |
+
licensed for non-commercial use only.
|
| 138 |
|
| 139 |
## Citation
|
| 140 |
|
| 141 |
```bibtex
|
| 142 |
+
@article{waibl2026eurosafeguard,
|
| 143 |
+
title = {EuroSafeGuard: Open Constitutional Classifiers},
|
| 144 |
+
author = {Waibl, Leonhard and Chen, Yen-Shan and Simko, Samuel and Michalak, Felix and
|
| 145 |
+
Diwan, Nirav and Singh, Arth and Thomas, Aditya and
|
| 146 |
+
Sch{\"o}lkopf, Bernhard and Jin, Zhijing},
|
| 147 |
+
journal = {arXiv preprint},
|
| 148 |
year = {2026}
|
| 149 |
}
|
| 150 |
```
|
__pycache__/eurosafeguard.cpython-312.pyc
DELETED
|
Binary file (20.1 kB)
|
|
|
eurosafeguard.py
CHANGED
|
@@ -7,6 +7,11 @@
|
|
| 7 |
system_message = g.system_note(category) # prepend to the protected model's context
|
| 8 |
blocked = g.check_reply(prompt, reply, decision) # True -> withhold the reply
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
Components (see README.md):
|
| 11 |
probe MLP probe on Llama-3.1-8B-Instruct activations (all 32 blocks) of the chat-formatted prompt; per-token
|
| 12 |
logits are smoothed with an EMA and the prompt score is their maximum.
|
|
@@ -16,6 +21,10 @@ Rule (alert-unless-CBRN): input score = mean of the probe and gate z-scores (z-s
|
|
| 16 |
alert_input_thr (flags 5% of WildChat-clean): refuse if the gate's top category is CBRN, otherwise answer under an
|
| 17 |
alert system note and block the reply if the exchange score exceeds exch_thr_20pct. Below: answer normally and block the
|
| 18 |
reply if the exchange score exceeds exch_thr_1pct.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
Memory: Llama-3.1-8B (~16 GB bf16) + Qwen3.5-9B for the gate (~19 GB) + Qwen3.5-9B for the exchange classifier (~19 GB).
|
| 21 |
Pass devices=("cuda:0", "cuda:1", "cuda:2") to spread them, or load_exchange=False to skip the reply check.
|
|
@@ -32,6 +41,22 @@ import torch
|
|
| 32 |
import torch.nn as nn
|
| 33 |
import torch.nn.functional as F
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
HEADS = ["CBRN", "Cyber", "Harm to Minors", "Harmful Manipulation", "Hate Speech", "Illegal Activities",
|
| 36 |
"Information Integrity", "Physical Harm", "Privacy", "Self-Harm", "Sexual Content"]
|
| 37 |
|
|
@@ -110,11 +135,16 @@ class LoRAClassifier:
|
|
| 110 |
|
| 111 |
# ----------------------------------------------------------------------------------------------------- guard
|
| 112 |
class EuroSafeGuard:
|
| 113 |
-
def __init__(self, root: str, seed: int = 0, devices=("cuda", "cuda", "cuda"), load_exchange: bool = True
|
|
|
|
| 114 |
from safetensors.torch import load_file
|
| 115 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 116 |
|
|
|
|
|
|
|
|
|
|
| 117 |
self.c = json.load(open(os.path.join(root, "thresholds", f"seed{seed}.json")))
|
|
|
|
| 118 |
self.tax = json.load(open(os.path.join(root, "taxonomy_11heads_short.json")))
|
| 119 |
pc = json.load(open(os.path.join(root, "probe", "probe_config.json")))
|
| 120 |
self.layers, self.M, self.max_len = pc["layers"], pc["M"], pc["max_len"]
|
|
@@ -133,7 +163,8 @@ class EuroSafeGuard:
|
|
| 133 |
return cls(repo_or_path, seed, **kw)
|
| 134 |
from huggingface_hub import snapshot_download
|
| 135 |
root = snapshot_download(repo_or_path, allow_patterns=["probe/*", f"gate/seed{seed}/*", "exchange/*",
|
| 136 |
-
f"thresholds/seed{seed}.json",
|
|
|
|
| 137 |
return cls(root, seed, **kw)
|
| 138 |
|
| 139 |
# -- input stage
|
|
@@ -196,6 +227,39 @@ class EuroSafeGuard:
|
|
| 196 |
t = [f"User: {a}\n\nAssistant: {b}" for a, b in zip(prompts, replies)]
|
| 197 |
return self.exchange.probs(t).max(1)
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
def check_reply(self, prompt, reply, decision="pass"):
|
| 200 |
"""True if the reply should be withheld. Stricter threshold for replies to alerted requests."""
|
| 201 |
single = isinstance(prompt, str)
|
|
|
|
| 7 |
system_message = g.system_note(category) # prepend to the protected model's context
|
| 8 |
blocked = g.check_reply(prompt, reply, decision) # True -> withhold the reply
|
| 9 |
|
| 10 |
+
# Full mode: the configuration of the paper's end-to-end table (EuroSafeGuard (Full)).
|
| 11 |
+
g = EuroSafeGuard.from_pretrained("EuroSafeAI/EuroSafeGuard", seed=0, mode="full")
|
| 12 |
+
escalated = g.escalate(prompt) # probe-only check, before generation
|
| 13 |
+
blocked = g.check_full(prompt, reply) # True -> withhold the reply
|
| 14 |
+
|
| 15 |
Components (see README.md):
|
| 16 |
probe MLP probe on Llama-3.1-8B-Instruct activations (all 32 blocks) of the chat-formatted prompt; per-token
|
| 17 |
logits are smoothed with an EMA and the prompt score is their maximum.
|
|
|
|
| 21 |
alert_input_thr (flags 5% of WildChat-clean): refuse if the gate's top category is CBRN, otherwise answer under an
|
| 22 |
alert system note and block the reply if the exchange score exceeds exch_thr_20pct. Below: answer normally and block the
|
| 23 |
reply if the exchange score exceeds exch_thr_1pct.
|
| 24 |
+
Rule (full, thresholds/full_seed*.json; reproduces the paper's end-to-end numbers): z-score probe, gate and exchange
|
| 25 |
+
on WildChat-clean. Only requests whose probe z exceeds probe_escalation_z (the WildChat-clean 90th percentile) are
|
| 26 |
+
escalated; an escalated exchange is blocked if 0.6*probe_z + 0.2*gate_z + 0.2*exchange_z > full_thr. Non-escalated
|
| 27 |
+
requests are never blocked, so the gate and the exchange classifier run only on escalated traffic.
|
| 28 |
|
| 29 |
Memory: Llama-3.1-8B (~16 GB bf16) + Qwen3.5-9B for the gate (~19 GB) + Qwen3.5-9B for the exchange classifier (~19 GB).
|
| 30 |
Pass devices=("cuda:0", "cuda:1", "cuda:2") to spread them, or load_exchange=False to skip the reply check.
|
|
|
|
| 41 |
import torch.nn as nn
|
| 42 |
import torch.nn.functional as F
|
| 43 |
|
| 44 |
+
W_FULL = np.array([0.6, 0.2, 0.2]) # probe, gate, exchange
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def full_block(probe, gate, exch, cfg) -> np.ndarray:
|
| 48 |
+
"""Full-mode decision from raw scores (probe EMA-max logit, gate max-head prob, exchange max-head prob).
|
| 49 |
+
True -> block. Mirrors scripts/e2e_compose_ext.py: score = W.z if probe_z > probe_escalation_z else -inf;
|
| 50 |
+
blocked if score > full_thr. Pass NaN (or anything) for gate/exch of non-escalated items; they are not used."""
|
| 51 |
+
Z = np.c_[(np.asarray(probe, dtype=float) - cfg["probe_mu"]) / cfg["probe_sd"],
|
| 52 |
+
(np.asarray(gate, dtype=float) - cfg["gate_mu"]) / cfg["gate_sd"],
|
| 53 |
+
(np.asarray(exch, dtype=float) - cfg["exch_mu"]) / cfg["exch_sd"]]
|
| 54 |
+
esc = Z[:, 0] > cfg["probe_escalation_z"]
|
| 55 |
+
score = np.full(len(Z), -np.inf)
|
| 56 |
+
score[esc] = Z[esc] @ W_FULL
|
| 57 |
+
return score > cfg["full_thr"]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
HEADS = ["CBRN", "Cyber", "Harm to Minors", "Harmful Manipulation", "Hate Speech", "Illegal Activities",
|
| 61 |
"Information Integrity", "Physical Harm", "Privacy", "Self-Harm", "Sexual Content"]
|
| 62 |
|
|
|
|
| 135 |
|
| 136 |
# ----------------------------------------------------------------------------------------------------- guard
|
| 137 |
class EuroSafeGuard:
|
| 138 |
+
def __init__(self, root: str, seed: int = 0, devices=("cuda", "cuda", "cuda"), load_exchange: bool = True,
|
| 139 |
+
mode: str = "alert"):
|
| 140 |
from safetensors.torch import load_file
|
| 141 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 142 |
|
| 143 |
+
if mode not in ("alert", "full"):
|
| 144 |
+
raise ValueError("mode must be 'alert' (input/alert rule) or 'full' (end-to-end rule)")
|
| 145 |
+
self.mode = mode
|
| 146 |
self.c = json.load(open(os.path.join(root, "thresholds", f"seed{seed}.json")))
|
| 147 |
+
self.cf = json.load(open(os.path.join(root, "thresholds", f"full_seed{seed}.json"))) if mode == "full" else None
|
| 148 |
self.tax = json.load(open(os.path.join(root, "taxonomy_11heads_short.json")))
|
| 149 |
pc = json.load(open(os.path.join(root, "probe", "probe_config.json")))
|
| 150 |
self.layers, self.M, self.max_len = pc["layers"], pc["M"], pc["max_len"]
|
|
|
|
| 163 |
return cls(repo_or_path, seed, **kw)
|
| 164 |
from huggingface_hub import snapshot_download
|
| 165 |
root = snapshot_download(repo_or_path, allow_patterns=["probe/*", f"gate/seed{seed}/*", "exchange/*",
|
| 166 |
+
f"thresholds/seed{seed}.json",
|
| 167 |
+
f"thresholds/full_seed{seed}.json", "*.json", "*.py"])
|
| 168 |
return cls(root, seed, **kw)
|
| 169 |
|
| 170 |
# -- input stage
|
|
|
|
| 227 |
t = [f"User: {a}\n\nAssistant: {b}" for a, b in zip(prompts, replies)]
|
| 228 |
return self.exchange.probs(t).max(1)
|
| 229 |
|
| 230 |
+
# -- full mode (paper's end-to-end configuration)
|
| 231 |
+
def _need_full(self):
|
| 232 |
+
if self.cf is None:
|
| 233 |
+
raise RuntimeError("load with mode='full'")
|
| 234 |
+
|
| 235 |
+
def escalate(self, prompt):
|
| 236 |
+
"""Probe-only escalation check (full mode): True if probe z > probe_escalation_z."""
|
| 237 |
+
self._need_full()
|
| 238 |
+
single = isinstance(prompt, str)
|
| 239 |
+
P = [prompt] if single else list(prompt)
|
| 240 |
+
e = (self.probe_scores(P) - self.cf["probe_mu"]) / self.cf["probe_sd"] > self.cf["probe_escalation_z"]
|
| 241 |
+
out = [bool(x) for x in e]
|
| 242 |
+
return out[0] if single else out
|
| 243 |
+
|
| 244 |
+
def full_scores(self, prompts: list[str], replies: list[str]) -> np.ndarray:
|
| 245 |
+
"""-> raw [N, 3] (probe, gate, exchange); gate and exchange are NaN for non-escalated items."""
|
| 246 |
+
self._need_full()
|
| 247 |
+
p = self.probe_scores(list(prompts))
|
| 248 |
+
esc = np.flatnonzero((p - self.cf["probe_mu"]) / self.cf["probe_sd"] > self.cf["probe_escalation_z"])
|
| 249 |
+
R = np.full((len(p), 3), np.nan); R[:, 0] = p
|
| 250 |
+
if len(esc):
|
| 251 |
+
R[esc, 1] = self.gate.probs([prompts[i] for i in esc]).max(1)
|
| 252 |
+
R[esc, 2] = self.exchange_scores([prompts[i] for i in esc], [replies[i] for i in esc])
|
| 253 |
+
return R
|
| 254 |
+
|
| 255 |
+
def check_full(self, prompt, reply):
|
| 256 |
+
"""Full mode: True if the exchange (prompt, reply) should be blocked."""
|
| 257 |
+
single = isinstance(prompt, str)
|
| 258 |
+
P, Rp = ([prompt], [reply]) if single else (list(prompt), list(reply))
|
| 259 |
+
R = self.full_scores(P, Rp)
|
| 260 |
+
out = [bool(x) for x in full_block(R[:, 0], R[:, 1], R[:, 2], self.cf)]
|
| 261 |
+
return out[0] if single else out
|
| 262 |
+
|
| 263 |
def check_reply(self, prompt, reply, decision="pass"):
|
| 264 |
"""True if the reply should be withheld. Stricter threshold for replies to alerted requests."""
|
| 265 |
single = isinstance(prompt, str)
|
thresholds/full_seed0.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"probe_mu": -6.815694701503735,
|
| 3 |
+
"probe_sd": 3.444286874679132,
|
| 4 |
+
"gate_mu": 0.03333534521305959,
|
| 5 |
+
"gate_sd": 0.10919123782498313,
|
| 6 |
+
"exch_mu": 0.02559613844389501,
|
| 7 |
+
"exch_sd": 0.090324355434096,
|
| 8 |
+
"probe_escalation_z": 1.164447941601081,
|
| 9 |
+
"probe_escalation_quantile": 0.9,
|
| 10 |
+
"weights": {
|
| 11 |
+
"probe": 0.6,
|
| 12 |
+
"gate": 0.2,
|
| 13 |
+
"exchange": 0.2
|
| 14 |
+
},
|
| 15 |
+
"full_thr": 3.3815571671026943,
|
| 16 |
+
"calibration": "EuroSafeGuard (Full), the configuration of the paper's end-to-end table. All z-scores use the mean and standard deviation on WildChat-clean (6,853 conversations); the exchange statistics use the exchange classifier's max-head probability on Llama-3-8B-Instruct replies to them. A request is escalated if its probe z exceeds probe_escalation_z (the WildChat-clean 90th percentile); an escalated exchange is blocked if 0.6*probe_z + 0.2*gate_z + 0.2*exchange_z > full_thr. full_thr is the smallest threshold that keeps every benign calibration set under its cap (fit half): WildChat-clean, MMLU, GSM8K, Alpaca, Dolly, OASST 0.5%; XSTest-safe, OR-Bench-80k 2%; OR-Bench-hard 5%; benign rows of styled jailbreak sets 40%. Reproduces scripts/e2e_compose_ext.py.",
|
| 17 |
+
"seed": 0
|
| 18 |
+
}
|
thresholds/full_seed1.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"probe_mu": -6.815694701503735,
|
| 3 |
+
"probe_sd": 3.444286874679132,
|
| 4 |
+
"gate_mu": 0.02970501206685616,
|
| 5 |
+
"gate_sd": 0.11025653741405626,
|
| 6 |
+
"exch_mu": 0.02559613844389501,
|
| 7 |
+
"exch_sd": 0.090324355434096,
|
| 8 |
+
"probe_escalation_z": 1.164447941601081,
|
| 9 |
+
"probe_escalation_quantile": 0.9,
|
| 10 |
+
"weights": {
|
| 11 |
+
"probe": 0.6,
|
| 12 |
+
"gate": 0.2,
|
| 13 |
+
"exchange": 0.2
|
| 14 |
+
},
|
| 15 |
+
"full_thr": 3.5707621643975536,
|
| 16 |
+
"calibration": "EuroSafeGuard (Full), the configuration of the paper's end-to-end table. All z-scores use the mean and standard deviation on WildChat-clean (6,853 conversations); the exchange statistics use the exchange classifier's max-head probability on Llama-3-8B-Instruct replies to them. A request is escalated if its probe z exceeds probe_escalation_z (the WildChat-clean 90th percentile); an escalated exchange is blocked if 0.6*probe_z + 0.2*gate_z + 0.2*exchange_z > full_thr. full_thr is the smallest threshold that keeps every benign calibration set under its cap (fit half): WildChat-clean, MMLU, GSM8K, Alpaca, Dolly, OASST 0.5%; XSTest-safe, OR-Bench-80k 2%; OR-Bench-hard 5%; benign rows of styled jailbreak sets 40%. Reproduces scripts/e2e_compose_ext.py.",
|
| 17 |
+
"seed": 1
|
| 18 |
+
}
|
thresholds/full_seed2.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"probe_mu": -6.815694701503735,
|
| 3 |
+
"probe_sd": 3.444286874679132,
|
| 4 |
+
"gate_mu": 0.03268957031742354,
|
| 5 |
+
"gate_sd": 0.12878089011617874,
|
| 6 |
+
"exch_mu": 0.02559613844389501,
|
| 7 |
+
"exch_sd": 0.090324355434096,
|
| 8 |
+
"probe_escalation_z": 1.164447941601081,
|
| 9 |
+
"probe_escalation_quantile": 0.9,
|
| 10 |
+
"weights": {
|
| 11 |
+
"probe": 0.6,
|
| 12 |
+
"gate": 0.2,
|
| 13 |
+
"exchange": 0.2
|
| 14 |
+
},
|
| 15 |
+
"full_thr": 3.131896552703142,
|
| 16 |
+
"calibration": "EuroSafeGuard (Full), the configuration of the paper's end-to-end table. All z-scores use the mean and standard deviation on WildChat-clean (6,853 conversations); the exchange statistics use the exchange classifier's max-head probability on Llama-3-8B-Instruct replies to them. A request is escalated if its probe z exceeds probe_escalation_z (the WildChat-clean 90th percentile); an escalated exchange is blocked if 0.6*probe_z + 0.2*gate_z + 0.2*exchange_z > full_thr. full_thr is the smallest threshold that keeps every benign calibration set under its cap (fit half): WildChat-clean, MMLU, GSM8K, Alpaca, Dolly, OASST 0.5%; XSTest-safe, OR-Bench-80k 2%; OR-Bench-hard 5%; benign rows of styled jailbreak sets 40%. Reproduces scripts/e2e_compose_ext.py.",
|
| 17 |
+
"seed": 2
|
| 18 |
+
}
|
thresholds/full_seed3.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"probe_mu": -6.815694701503735,
|
| 3 |
+
"probe_sd": 3.444286874679132,
|
| 4 |
+
"gate_mu": 0.03866907581677466,
|
| 5 |
+
"gate_sd": 0.13433606451867683,
|
| 6 |
+
"exch_mu": 0.02559613844389501,
|
| 7 |
+
"exch_sd": 0.090324355434096,
|
| 8 |
+
"probe_escalation_z": 1.164447941601081,
|
| 9 |
+
"probe_escalation_quantile": 0.9,
|
| 10 |
+
"weights": {
|
| 11 |
+
"probe": 0.6,
|
| 12 |
+
"gate": 0.2,
|
| 13 |
+
"exchange": 0.2
|
| 14 |
+
},
|
| 15 |
+
"full_thr": 3.097429574322379,
|
| 16 |
+
"calibration": "EuroSafeGuard (Full), the configuration of the paper's end-to-end table. All z-scores use the mean and standard deviation on WildChat-clean (6,853 conversations); the exchange statistics use the exchange classifier's max-head probability on Llama-3-8B-Instruct replies to them. A request is escalated if its probe z exceeds probe_escalation_z (the WildChat-clean 90th percentile); an escalated exchange is blocked if 0.6*probe_z + 0.2*gate_z + 0.2*exchange_z > full_thr. full_thr is the smallest threshold that keeps every benign calibration set under its cap (fit half): WildChat-clean, MMLU, GSM8K, Alpaca, Dolly, OASST 0.5%; XSTest-safe, OR-Bench-80k 2%; OR-Bench-hard 5%; benign rows of styled jailbreak sets 40%. Reproduces scripts/e2e_compose_ext.py.",
|
| 17 |
+
"seed": 3
|
| 18 |
+
}
|
thresholds/full_seed4.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"probe_mu": -6.815694701503735,
|
| 3 |
+
"probe_sd": 3.444286874679132,
|
| 4 |
+
"gate_mu": 0.020549819204687854,
|
| 5 |
+
"gate_sd": 0.1115630808512582,
|
| 6 |
+
"exch_mu": 0.02559613844389501,
|
| 7 |
+
"exch_sd": 0.090324355434096,
|
| 8 |
+
"probe_escalation_z": 1.164447941601081,
|
| 9 |
+
"probe_escalation_quantile": 0.9,
|
| 10 |
+
"weights": {
|
| 11 |
+
"probe": 0.6,
|
| 12 |
+
"gate": 0.2,
|
| 13 |
+
"exchange": 0.2
|
| 14 |
+
},
|
| 15 |
+
"full_thr": 3.318287213890092,
|
| 16 |
+
"calibration": "EuroSafeGuard (Full), the configuration of the paper's end-to-end table. All z-scores use the mean and standard deviation on WildChat-clean (6,853 conversations); the exchange statistics use the exchange classifier's max-head probability on Llama-3-8B-Instruct replies to them. A request is escalated if its probe z exceeds probe_escalation_z (the WildChat-clean 90th percentile); an escalated exchange is blocked if 0.6*probe_z + 0.2*gate_z + 0.2*exchange_z > full_thr. full_thr is the smallest threshold that keeps every benign calibration set under its cap (fit half): WildChat-clean, MMLU, GSM8K, Alpaca, Dolly, OASST 0.5%; XSTest-safe, OR-Bench-80k 2%; OR-Bench-hard 5%; benign rows of styled jailbreak sets 40%. Reproduces scripts/e2e_compose_ext.py.",
|
| 17 |
+
"seed": 4
|
| 18 |
+
}
|