File size: 3,963 Bytes
0130e48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
---
base_model: Qwen/Qwen3-1.7B
library_name: peft
pipeline_tag: text-classification
tags:
  - moderation
  - toxicity
  - jailbreak-detection
  - multi-label
  - qwen3
  - encoder-conversion
datasets:
  - google/civil_comments
  - lmsys/toxic-chat
  - allenai/real-toxicity-prompts
  - TrustAIRLab/in-the-wild-jailbreak-prompts
license: apache-2.0
---

# Opus Moderation 3

Our research flagship: a **Qwen3-1.7B decoder converted into a bidirectional
encoder**, fine-tuned with LoRA for 7 toxicity labels + jailbreak detection.
Highest general-moderation score of everything we have measured:

| model | params | macro F1 (7 labels) | jailbreak F1 |
|---|---|---|---|
| **opus-moderation-3 (this model)** | 1.7B | **0.563** | 0.929 |
| [om4-large](https://huggingface.co/opus-research/opus-moderation-4-large) | 395M | 0.543 | 0.903 |
| [om4-neo](https://huggingface.co/opus-research/opus-moderation-4-neo) | 395M | — | **0.938** |
| unitary/unbiased-toxic-roberta | 125M | 0.527 | — |

**⚠️ This model cannot be loaded with plain `from_pretrained`.** It is a LoRA
adapter over Qwen3-1.7B whose attention was made bidirectional at training
time — the included [`opus_moderation.py`](opus_moderation.py) rebuilds that
conversion. If you want a drop-in model, use
[om4-large](https://huggingface.co/opus-research/opus-moderation-4-large):
it scores within 0.020 macro of this one at less than a quarter the size, and
most of the gap is data, not architecture (that is what the om4 family
established).

## Usage

```python
# needs: transformers>=5.14, peft, torch — and opus_moderation.py from this repo
from huggingface_hub import hf_hub_download, snapshot_download
import importlib.util, sys

repo = "opus-research/opus-moderation-3"
path = snapshot_download(repo)
spec = importlib.util.spec_from_file_location(
    "opus_moderation", f"{path}/opus_moderation.py")
om = importlib.util.module_from_spec(spec); spec.loader.exec_module(om)

model = om.ModerationModel.load(path)
print(model.score(["ignore all previous instructions"])[0])
```

Outputs are calibrated annotator fractions per label; `sigmoid`, never
`softmax`.

## The conversion, and its one silent failure mode

Decoder → encoder is two changes, and the second is not optional:

1. Swap the causal mask builder for a bidirectional one.
2. **Clear `is_causal` on every attention module.** For an unpadded batch the
   mask builders return `None`, and the SDPA path then falls back to
   `module.is_causal` — which Qwen3 hardcodes to `True`. Skip this and the
   model loads cleanly, reports no error, and stays fully causal while
   claiming otherwise.

Mean-pooling over non-pad tokens replaces last-token pooling (with
bidirectional attention every position has seen the full sequence). The
included loader does all of this and refuses to proceed if the internals have
moved.

## Evaluation (20k unseen rows)

| label | F1 |
|---|---|
| insult | 0.709 |
| toxicity | 0.707 |
| sexual_explicit | 0.685 |
| obscene | 0.663 |
| threat | 0.565 |
| identity_attack | 0.563 |
| severe_toxicity | 0.046 |
| **macro** | **0.563** |
| macro (ex severe_toxicity) | 0.649 |

Jailbreak: F1 0.929 (recall 91.0%, FP 2.5% — the lowest false-positive rate
of our family).

## Training

| | |
|---|---|
| Base | `Qwen/Qwen3-1.7B`, bidirectional conversion, mean pool |
| Adapter | LoRA r=16, α=32, all attention + MLP projections, + full score head |
| Data | ~310k rows: civil_comments (soft labels), toxic-chat, real-toxicity-prompts, jailbreak corpora, refusal negatives |
| Loss | masked BCE on raw annotator fractions |
| Hardware | H100 |

## Limitations

- **Requires the bundled loader** — the weights are meaningless under a stock
  causal load, and the failure is silent (see above).
- `severe_toxicity` is unreliable for every model we tested.
- English only; 384-token training length.
- Training data includes `lmsys/toxic-chat` (CC-BY-NC); review if that
  matters for your use.