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 395M 0.543 0.903
om4-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 rebuilds that conversion. If you want a drop-in model, use om4-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

# 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.
Downloads last month
8
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for opus-research/opus-moderation-3

Finetuned
Qwen/Qwen3-1.7B
Adapter
(581)
this model

Datasets used to train opus-research/opus-moderation-3