Instructions to use desert-ant-labs/toxic with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use desert-ant-labs/toxic with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
- toxic: on-device multilingual hate-speech and abuse triage
- Files
- Taxonomy
- Architecture
- Measured quality (the shipped artifacts, not the checkpoint)
- How it compares (measured, same scorer, same suite)
- Per-language, head to head (measured)
- False positives in context
- Threshold menu (per-language operating points, torch reference)
- Failure modes (read before deploying)
- Per-language deep-dive (real-MHC, flagship LiteRT int4b32 artifact)
- Fairness across protected groups
- How these numbers were made
- License
- Files
toxic: on-device multilingual hate-speech and abuse triage
A multilingual toxicity classifier for on-device hate speech detection and
content moderation triage: five content heads (HATEFUL, HARASSMENT,
THREAT, SEXUAL, SELF_HARM) plus protected-group target heads, covering
23 languages. Inference is 100% on-device: no text leaves the device to be
scored.
Headline: 0.785 macro-F1 averaged over seven real-Multilingual-HateCheck languages (German, French, Dutch, Portuguese, Spanish, Italian, Polish), measured on the torch reference. The shipped, quantized artifacts hold 0.777 (iOS Core ML int4, Android LiteRT int4) to 0.769 (web ONNX int4e) of that same 7-language average, re-measured on the exact bytes that ship. On the seven non-English languages, toxic's shipped artifacts beat every runnable competitor build (full-precision and int4 alike) by 12 to 29 points of macro-F1, at a fraction of the disk. Full tables are in How it compares.
Provenance is load-bearing in this card and never blurred. Eight of the 23 languages (English plus the 7-EU set) are scored on real Multilingual HateCheck, the published, peer-reviewed benchmark. The other 15 are scored on translate-and-audit synthetic evaluation sets built for this project; those numbers are development-benchmark estimates, not MHC-comparable, and are never averaged into the 7-EU headline. Three of the 15 (Lithuanian, Latvian, Estonian) carry an extra caveat: even the translation audit could not fully verify them. Every table below labels which tier a number belongs to.
Looking for English only? See
toxic-en, the English
specialist. Same idea as Whisper's .en models: this multilingual model
covers English too (0.771 shipped, LiteRT int4), but a model trained only on
English does the job better for English-first apps (toxic-en ships 0.808
to 0.817). Use toxic when you need coverage across languages in one model;
use toxic-en when your traffic is English and you want the extra points.
Quality numbers throughout are macro-F1 on HateCheck and its multilingual extension for the real-MHC languages, and on translate-and-audit synthetic sets for the other 15: hand-written or translated test cases built to trap classifiers with hard hate (slurs, spelling evasion, implied hate) and deliberately confusing non-hate (quotes, counter-speech, negation). Macro-F1 averages the score for catching hate with the score for letting clean text through, so a model cannot look good by simply flagging everything (or nothing); 1.000 is perfect. HateCheck is eval-only: never trained on.
Triage, not verdict. Outputs are escalation signals for human review or a heavier local tier, not autonomous removal decisions. The failure modes below are published on purpose: read them before wiring the model into any enforcement path.
Only HATEFUL is benchmarked. Every number in this card is the HATEFUL
head. HARASSMENT, THREAT and SEXUAL have never been measured against any
external baseline or any held-out human-adjudicated set, so this card makes no
quality claim about them and you should not infer one. An earlier version of
this card described THREAT as "a high-precision head intended for resource
routing with a human in the loop". No measurement supported that, and it has
been removed rather than restated.
Do not use SELF_HARM in this release, and treat SEXUAL as unvalidated.
The v0.2.0 training mix contains zero positive SELF_HARM examples and only
884 positive SEXUAL examples out of 563,197 rows. Measured on 3,728 English
HateCheck rows, SELF_HARM never exceeds 0.008 and SEXUAL fires once, so
SELF_HARM has effectively zero recall and cannot route anything. If you need
self-harm detection, do not rely on this head. Both are being removed from the
flagged rule or retrained before the next release.
HARASSMENT over-fires on English. It was trained against a generic
toxicity mapping, so it fires on undirected profanity ("I'm so fucking
excited"), on counter-speech that quotes hate in order to condemn it, and on
reclaimed in-group usage. Gating on flagged gives a false-positive rate of
0.43 on English HateCheck against 0.11 for hateful alone. Gate on hateful
until this is fixed; raising the HARASSMENT threshold does not fix it,
because the head ranks undirected profanity above directed abuse.
Files
| File | Format | Size | Contents |
|---|---|---|---|
toxic.tflite |
LiteRT / TFLite (int4 blockwise-32) | ~90.5 MB | Android and Linux, native LiteRT kernels, 7-EU 0.777 |
toxic.onnx |
ONNX (int4e) | ~100.1 MB | Browser artifact for ONNX Runtime Web, 7-EU 0.769 |
toxic.mlmodelc |
Core ML package (4-bit palettized) | ~79.9 MB | iOS / macOS, ANE-resident, 7-EU 0.777 |
model.int8.onnx |
ONNX (int8) | ~159.5 MB | On request; higher fidelity (7-EU 0.783), larger |
model.int8.tflite |
LiteRT (int8) | ~162.0 MB | On request; not yet HateCheck-measured |
model.pt |
PyTorch state dict (fp32) | ~609 MB | Torch reference weights (159M params) |
config.json |
JSON | tiny | Encoder + head config (base, labels, max_len, vocab size) |
tokenizer.json, tokenizer_config.json |
JSON | ~ | Trimmed (95,552-piece) SentencePiece-Unigram tokenizer (XLM-R lineage) |
labels.json |
JSON | tiny | id2label / label2id for both heads |
toxic_meta.json |
JSON | tiny | Version, languages, labels, default threshold and the per-language threshold menu |
Taxonomy
Two multi-label heads over one shared encoder. Text can fire several labels at once; thresholds apply per label.
Content heads (5):
HATEFUL(scoped to the EU notion of illegal hate speech: public incitement to violence or hatred against a protected group, Framework Decision 2008/913/JHA),HARASSMENT(directed abuse: insult or bullying aimed at a person or a group; aHATEFULmessage is normally alsoHARASSMENT),THREAT(threat of violence),SEXUAL(sexual harassment / unsolicited sexual aggression),SELF_HARM(self-harm / suicide content, for resource routing).HARASSMENTchanged meaning in taxonomy v3. It previously read "targeted insult / bullying of an individual (not group-protected)", which described it as a sibling ofHATEFUL. It is now a superset: both human-annotated training corpora treat an attack on a protected group as also being abuse directed at someone. Integrations that presented the two heads as mutually exclusive need updating.SELF_HARMis untrained. Do not use it. No registered training corpus annotates self-harm, so the head has no positive examples and no supervised negatives either: it is untrained, not merely rare. Its output is whatever the shared trunk happens to produce. Measured on 3,728 English HateCheck rows it never exceeds 0.008, so in practice it does not fire, but that is an observation about this checkpoint and not a guarantee.Be precise about how it is switched off, because it depends which file you read. The SDKs bundle a sidecar meta that pins this head's threshold to 1.0, which a sigmoid cannot reach. The
toxic_meta.jsonpublished in this repo carries no per-head threshold block at all, so a consumer that reads the published meta directly and applies the usual 0.5 default gets an untrained head wired into a flag-if-any-head-fires contract. If you built your own pipeline on this file rather than on one of our SDKs, pinSELF_HARMyourself. The next artifact carries the threshold block in the published meta so both paths agree; this one does not.Do not route self-harm resources off this output under any configuration.
Target heads (10): the 2008/913/JHA protected grounds
RACE,COLOUR,RELIGION,DESCENT,NATIONAL_ETHNIC_ORIGIN, plus the extended groundsSEXUAL_ORIENTATION,GENDER,DISABILITY,AGE,OTHER. Only meaningful whenHATEFULfires; used for per-ground fairness reporting.
Architecture
- Encoder: XLM-R-base lineage (12 layers, hidden 768), vocabulary trimmed from 250k to 95,552 pieces. 159M total params (73M embedding + 86M body), fine-tuned with focal BCE on the two heads.
- Static shapes: fixed
[1, 128]input, no dynamic control flow; ANE-native reformulation for the Core ML export, int4 for the LiteRT and ONNX exports. - Runtime: tokenize off-graph (SentencePiece-Unigram,
tokenizer.json), run the encoder, apply sigmoid per label, threshold per the menu below. Default threshold 0.40.
Phone-class latency (iPhone 13/15, mid-range Android) is not yet measured for this release.
Measured quality (the shipped artifacts, not the checkpoint)
Every number below is HateCheck (or, for the 15 synthetic languages, the translate-and-audit synthetic set) re-run on the exact artifact in this repo, threshold 0.40.
7-EU headline (real Multilingual HateCheck: de, fr, nl, pt, es, it, pl)
| platform | ship artifact | size | 7-EU macro-F1 | en macro-F1 | all-23 (secondary) |
|---|---|---|---|---|---|
| iOS / macOS (Core ML) | toxic.mlmodelc |
79.9 MB | 0.777 | 0.719 | (8-lang only) |
| Android / Linux (LiteRT) | toxic.tflite |
90.5 MB | 0.777 | 0.771 | 0.743 |
| Web (ONNX) | toxic.onnx |
100.1 MB | 0.769 | 0.758 | 0.739 |
| On request (ONNX int8) | model.int8.onnx |
159.5 MB | 0.783 | 0.759 | 0.748 |
| Reference | model.pt (torch) |
~609 MB (fp32) | 0.785 | 0.779 | 0.755 |
all-23 blends the real-MHC 8 languages with the 15 synthetic-eval
languages and is a secondary coverage figure, not comparable to the 7-EU
headline; treat it as a rough sense of "how does this do everywhere," not a
quality claim.
Core ML palettization (4-bit) costs the most on English: 0.719 vs the 0.779
torch reference, a 6-point drop, while still holding the 7-EU headline at
0.777, tied with the Android build. English is where int4 quantization hurts
most across every platform we ship. Per-language source tables:
reports/v2ship_coreml_int4_real8.md, reports/v2ship_tflite_int4b32.md,
reports/v2ship_onnx_int4e.md, reports/v2ship_onnx_int8.md, and the torch
reference in reports/E24_trimmed_multilingual.md.
Per-language, real-MHC (threshold 0.40)
| lang | torch | LiteRT int4b32 | Core ML int4 | ONNX int4e |
|---|---|---|---|---|
| en | 0.779 | 0.771 | 0.719 | 0.758 |
| de | 0.814 | 0.809 | 0.800 | 0.805 |
| fr | 0.819 | 0.809 | 0.792 | 0.797 |
| nl | 0.780 | 0.774 | 0.780 | 0.765 |
| pt | 0.768 | 0.760 | 0.783 | 0.756 |
| es | 0.781 | 0.771 | 0.778 | 0.764 |
| it | 0.772 | 0.763 | 0.772 | 0.754 |
| pl | 0.759 | 0.750 | 0.735 | 0.744 |
| 7-EU mean | 0.785 | 0.777 | 0.777 | 0.769 |
The 15 synthetic-eval languages (translate-and-audit, NOT MHC-comparable)
These are not scored on real HateCheck. They are scored on a
translate-and-audit synthetic evaluation set built for this project
(data/eval/mhc_v2/), used as a development benchmark. Numbers below are
useful for relative comparison across this model's own artifacts, not for
comparison against any real-MHC number or any competitor. Lithuanian,
Latvian and Estonian carry an extra caveat: the translation audit could not
fully verify them, so treat those three as provisional.
| lang | torch (synthetic) | LiteRT int4b32 | ONNX int4e |
|---|---|---|---|
| da | 0.818 | 0.806 | 0.809 |
| sv | 0.808 | 0.808 | 0.801 |
| ro | 0.790 | 0.778 | 0.786 |
| bg | 0.782 | 0.765 | 0.757 |
| hr | 0.759 | 0.741 | 0.749 |
| el | 0.743 | 0.741 | 0.727 |
| sk | 0.742 | 0.720 | 0.725 |
| cs | 0.737 | 0.718 | 0.718 |
| fi | 0.718 | 0.697 | 0.685 |
| et (caveated) | 0.718 | 0.702 | 0.708 |
| lv (caveated) | 0.716 | 0.699 | 0.700 |
| sl | 0.715 | 0.690 | 0.695 |
| hu | 0.708 | 0.698 | 0.690 |
| lt (caveated) | 0.676 | 0.664 | 0.659 |
| ga | 0.661 | 0.655 | 0.655 |
Source: reports/v2ship_tflite_int4b32.md, reports/v2ship_onnx_int4e.md,
reports/E24_trimmed_multilingual.md (torch).
How it compares (measured, same scorer, same suite)
Every runnable competitor was re-run through the identical HateCheck harness, in both its full-precision (bf16) build and its shipping int4 quant, so quantized is compared against quantized and full against full. Generative guards are scored on their parsed safety verdict (Unsafe = hate); their on-disk sizes are published Q4_K_M GGUF bytes. 7-EU = mean over de, fr, nl, pt, es, it, pl.
| system | build | on-disk size | 7-EU macro-F1 | en macro-F1 |
|---|---|---|---|---|
| toxic | LiteRT int4b32 / Core ML int4 (shipped) | 90.5 / 79.9 MB | 0.777 | 0.771 / 0.719 |
| toxic | torch reference | ~609 MB (fp32) | 0.785 | 0.779 |
| Llama-Guard-3-1B | bf16 (server) | n/a | 0.649 | 0.814 |
| Llama-Guard-3-1B | int4 | 955.4 MB | 0.633 | 0.794 |
| Qwen3Guard-Gen-0.6B | bf16 (server) | n/a | 0.652 | 0.770 |
| Qwen3Guard-Gen-0.6B | int4 | 484.2 MB | 0.622 | 0.750 |
| Detoxify multilingual | fp32 | ~280 MB | 0.487 | 0.642 |
What the table says:
- On the seven non-English languages, toxic's shipped artifacts beat every competitor build outright, at roughly 5 to 12x less disk (Core ML int4 79.9 MB / LiteRT int4b32 90.5 MB vs Qwen3Guard int4 484.2 MB and Llama-Guard int4 955.4 MB). The margin is never small: shipped toxic (7-EU 0.777) beats Llama-Guard bf16 (0.649) by 12.8 points and Llama-Guard int4 (0.633, 955 MB) by 14.4 points; it beats Qwen3Guard bf16 (0.652) by 12.5 points and Detoxify fp32 (0.487) by 29 points.
- On English, the competitors' server bf16 builds lead. Llama-Guard-3-1B
bf16 scores 0.814 on English vs toxic's 0.779 torch reference and
0.719-0.771 shipped. This is exactly the gap
toxic-enis built to close: see the English specialist card for numbers that match or beat Llama-Guard's own shipping int4 build. - Detoxify multilingual, the incumbent same-architecture-class classifier, trails toxic's shipped artifacts on every one of the 7-EU languages, and the gap is uneven: 12-17 points in French, Portuguese, Spanish and Italian, but it effectively collapses in German, Dutch and Polish (48-52 points behind).
Source: reports/leaderboard.md, reports/V2_RELEASE_DATASHEET.md.
Per-language, head to head (measured)
The aggregate above hides where the difference actually comes from, so here is every shipped language against the two generative guards, one row per language.
How these numbers were produced
- Our real decision rule, not a favourable one. These rows score the
shipped artifact exactly as the SDK does:
flaggedfires when ANY content head clears its own threshold fromtoxic_meta.json(HATEFUL 0.40, the other four heads 0.50). The tables higher up this card report the HATEFUL head alone, which is the HateCheck literature convention and the right number to compare against published HateCheck results. Here we use the product rule because a generative guard answers one question, "is this unsafe", and is credited when it flags for any reason. Scoring only our HATEFUL head against that would be asymmetric. The two conventions differ by about 2 points on 7-EU (0.777 vs 0.801) and under 1 point on the synthetic tier. - That rule costs us on false positives, and we report it. Flagging on any
head raises recall and also raises the false-positive rate: on 7-EU from 0.25
to 0.29, and on English from 0.108 to 0.427, because heads like HARASSMENT
fire on reclaimed slurs and profanity-without-hate that HateCheck labels
non-hateful. If you gate purely on
flagged, that is the rate you get; gate onhatefulfor the narrower signal. - One harness, no per-language tuning. Every system is scored by
toxic_training.hatecheck.scoreson the same sentences, metric is macro-F1 over the hateful / non-hateful decision, so a model cannot win by flagging everything. Neither we nor the competitors get per-language threshold tuning. - Competitors are shown at their best. Llama-Guard-3-1B and Qwen3Guard-Gen-0.6B are their full-precision bf16 server builds, not the quantized weights you would ship on a phone. Qwen also emits a third verdict, "Controversial"; we score it whichever way is more favourable to Qwen per language, so these margins are the conservative ones.
- Generative guards are read, not thresholded. Both are prompted with their own chat template and decoded greedily; the parsed safety verdict is the prediction. Unparsable generations would count against the guard, though in practice there were none.
- Two provenance tiers, never mixed. The first table is published Multilingual HateCheck (Röttger et al., WOAH 2022). The second is our own translate-and-audit set for the fifteen languages MHC does not cover: every system is scored on identical sentences, so the comparison between systems is sound, but the absolute values are not comparable to real MHC and are never averaged into a real-MHC figure.
Known limits of the synthetic tier
A seven-way independent audit of those fifteen sets (roughly 1,000 rows read by native-language reviewers) found no gold-label errors, no encoding corruption and no untranslated rows, but three limits worth stating plainly:
- Part of the training data for these same fifteen languages was generated by the same model that built this eval set. We verified zero exact and zero near-duplicate overlap between them, and the untrimmed reference model scores higher than the shipped one, so vocabulary selection bought nothing. A shared-generator style effect still cannot be ruled out, so treat this tier as directional and the real-MHC tier as authoritative.
- About 21% of rows are deliberate character-obfuscation tests (leetspeak, inserted spaces, character swaps). Our lead is much wider there, because the SDK normalizes text before tokenizing and the guards receive it raw. On clean, unobfuscated rows the margin is roughly +20 points rather than +57.
- Irish is missing most of its
slur_hrows (16 against 144 for every other language) and all ofslur_homonym_nh. The Irish headline holds, the Irish slur sub-slice is not a fair comparison point.
Real Multilingual HateCheck (authoritative) published Röttger et al. sets
| lang | toxic (shipped rule) | Llama-Guard-3-1B | Qwen3Guard-Gen-0.6B | toxic vs best |
|---|---|---|---|---|
| en (English) | 0.767 | 0.814 | 0.770 | -4.7 |
| de (German) | 0.827 | 0.686 | 0.674 | +14.0 |
| fr (French) | 0.825 | 0.654 | 0.691 | +13.4 |
| nl (Dutch) | 0.807 | 0.668 | 0.647 | +13.9 |
| pt (Portuguese) | 0.798 | 0.684 | 0.671 | +11.4 |
| es (Spanish) | 0.797 | 0.685 | 0.692 | +10.5 |
| it (Italian) | 0.772 | 0.597 | 0.642 | +13.0 |
| pl (Polish) | 0.784 | 0.566 | 0.547 | +21.7 |
| mean (8 languages) | 0.797 | 0.669 | 0.667 | +12.7 |
Translate-and-audit sets (synthetic-eval provenance) our own sets, NOT comparable to real MHC
| lang | toxic (shipped rule) | Llama-Guard-3-1B | Qwen3Guard-Gen-0.6B | toxic vs best |
|---|---|---|---|---|
| da (Danish) | 0.811 | 0.505 | 0.529 | +28.3 |
| sv (Swedish) | 0.825 | 0.581 | 0.550 | +24.4 |
| fi (Finnish) | 0.704 | 0.375 | 0.485 | +22.0 |
| hu (Hungarian) | 0.703 | 0.485 | 0.525 | +17.8 |
| cs (Czech) | 0.716 | 0.594 | 0.556 | +12.2 |
| sk (Slovak) | 0.720 | 0.471 | 0.524 | +19.5 |
| sl (Slovenian) | 0.690 | 0.399 | 0.492 | +19.8 |
| hr (Croatian) | 0.745 | 0.443 | 0.485 | +26.0 |
| bg (Bulgarian) | 0.773 | 0.503 | 0.550 | +22.3 |
| el (Greek) | 0.745 | 0.576 | 0.538 | +16.9 |
| ro (Romanian) | 0.786 | 0.576 | 0.572 | +21.1 |
| lt (Lithuanian) | 0.676 | 0.316 | 0.511 | +16.5 |
| lv (Latvian) | 0.706 | 0.333 | 0.484 | +22.2 |
| et (Estonian) | 0.708 | 0.303 | 0.501 | +20.7 |
| ga (Irish) | 0.654 | 0.234 | 0.281 | +37.4 |
| mean (15 languages) | 0.731 | 0.446 | 0.505 | +22.5 |
False positives in context
Two different numbers describe "how often it flags clean text," and conflating them misreads the model.
On adversarial trap cases: roughly 20 to 27%, real-MHC 7-EU languages, at
threshold 0.40. HateCheck's non-hate cases are not ordinary text; they are
traps built from the exact patterns that break hate classifiers:
counter_quote / counter_ref quote or denounce hate to condemn it,
slur_reclaimed is in-group reclaimed slur use, negate_neg is negated hate
("no one should ever hurt them"). On the flagship LiteRT artifact the
overall non-hate false-positive rate ranges from 19.6% (Dutch) to 27.4%
(Italian) across the 7-EU languages at threshold 0.40
(reports/v2ship_tflite_int4b32.md).
On natural text: single digits. On the held-out test split of real
corpora (Civil Comments, Measuring Hate Speech, GAHD; never seen in
training), the HATEFUL head fires on 1.7% of clean rows overall, and any of
the five heads fires on 7.7% (the extra fires are mostly HARASSMENT at
6.7%). The one natural-corpus outlier is German GAHD at 26.6%, and that is
by design: GAHD is an adversarially constructed dataset of contrast pairs
(quotes, counter-speech, borderline rephrasings), so it measures the trap
rate, not the organic rate. Source: reports/E24_extended.md.
Beware the low-FPR trap when comparing. Llama-Guard-3-1B posts a low
average non-hate false-positive rate on the seven non-English languages in
bf16 (0.187, averaged from the per-language rows in
reports/leaderboard.md), noticeably lower than toxic's own 7-EU FPR range
(0.196-0.274 on the shipped LiteRT artifact, above). But Llama-Guard's
7-EU macro-F1 is only 0.649, 12.8 points below toxic's 0.777 shipped, which
means the low FPR is bought with weaker hate-class performance, not
superior precision at matched recall. A low false-positive rate bought by
under-firing is not the same as good triage, which is why the headline
metric is macro-F1 (balanced by construction across both classes) and why a
low FPR number should always be read next to the paired macro-F1. If your
application needs a lower FPR from toxic specifically, buy it explicitly
with the threshold menu below and keep the recall cost on the table.
Threshold menu (per-language operating points, torch reference)
macro-F1 is the 2-class macro; FPR is the false-positive rate on
non-hate cases, the number to watch if you fear over-blocking. Full sweep
(0.30 to 0.70, all 23 languages) is in reports/E24_sweep.md.
| lang | tuned threshold | macro-F1 @ tuned | FPR @ tuned | macro-F1 @ 0.40 | FPR @ 0.40 |
|---|---|---|---|---|---|
| en | 0.30 | 0.811 | 0.200 | 0.779 | 0.135 |
| de | 0.40 | 0.814 | 0.260 | 0.814 | 0.260 |
| fr | 0.50 | 0.819 | 0.199 | 0.819 | 0.225 |
| nl | 0.35 | 0.782 | 0.220 | 0.780 | 0.204 |
| pt | 0.30 | 0.772 | 0.286 | 0.768 | 0.260 |
| es | 0.50 | 0.784 | 0.230 | 0.781 | 0.265 |
| it | 0.30 | 0.774 | 0.278 | 0.772 | 0.248 |
| pl | 0.30 | 0.764 | 0.281 | 0.759 | 0.258 |
At per-language tuned thresholds, the 7-EU average (de, fr, nl, pt, es, it,
pl) rises to 0.787 from 0.785 at the flat 0.40 default; French, German and
Spanish clear 0.78-0.82 at their tuned point. Every language's improvement
from tuning is small (a few tenths of a point) because 0.40 is already
close to optimal almost everywhere; the exception is English, where dropping
to 0.30 buys +3.2 points of macro-F1 at the cost of a higher FPR (0.200 vs
0.135). The shipped default is a flat 0.40 for every language (no threshold
is fit to the benchmark); the full per-language sweep for all 23 languages
(0.30 to 0.70) is in reports/E24_sweep.md as tuning guidance, so the
operating point stays yours to set per application.
Failure modes (read before deploying)
This section is the practical read: what actually goes wrong, what it looks like in an app, and what to do about it. Publishing these is part of the product.
1. It sometimes flags people quoting or condemning hate. News reports,
moderation discussions, and counter-speech repeat the hateful words they are
arguing against, and the model reacts to the words. On the real-MHC
languages, counter_quote_nh and counter_ref_nh false-positive rates on
the flagship LiteRT artifact range from roughly 7% (Dutch) to 53% (German,
counter_ref_nh). Mitigation: route flags to human review or a heavier
model; never auto-remove on this signal alone.
2. Positive or neutral mentions of identity groups can trip it, especially
outside English. Sentences like "Proud to be a trans woman and having a
great day" are sometimes flagged simply because an identity term appears.
ident_pos_nh false-positive rates on the flagship LiteRT artifact range
from 23% (Spanish) to 37% (Polish) across the 7-EU languages, and
target_group_nh false positives run from 28% (German) to 46% (Italian).
Mitigation: use the higher per-language thresholds from the menu; treat
short identity-statement texts as low-confidence.
3. Reclaimed slurs and lookalike words, most measurable in English. The
real Multilingual HateCheck translation only carries the reclaimed-slur and
slur-homonym trap cells (slur_reclaimed_nh, slur_homonym_nh) for
English, not for the other 7 real-MHC languages, so this failure mode is
only directly measured there: on the flagship LiteRT artifact English scores
7% FPR on reclaimed slurs and 33% FPR on slur homonyms. Mitigation: human
review for user-report flows in communities where reclaimed language is
common.
4. Bare-slur hate is a harder hate pattern than direct insults, especially
in English. slur_h accuracy (an error here is a miss) on the flagship
LiteRT artifact ranges from 0.667 (English, the worst) to 0.907 (Italian,
the best); Polish (0.705) and German (0.758) also trail. Mitigation: if
slur coverage is critical, pair the model with a per-language slur
wordlist; the two failure profiles are complementary.
5. Hate phrased as a question or an implication is harder than direct
insults. phrase_question_h accuracy on the flagship LiteRT artifact is
0.471-0.700 across the 7-EU languages, and derog_impl_h (implied
derogation) is 0.586-0.890, well below direct threat detection
(threat_dir_h, threat_norm_h), which is 0.89-1.00 in every language
measured. Mitigation: none needed for triage use; do not promise users that
subtle hate is always caught.
6. Quantization costs are largest on English, across every platform.
Core ML 4-bit palettization costs English 6.0 points (0.719 vs 0.779 torch)
while holding the 7-EU average flat (0.777 vs 0.785 torch, a smaller
0.8-point loss). LiteRT int4b32 costs English 0.8 points (0.771 vs 0.779).
ONNX int4e costs English 2.1 points (0.758 vs 0.779); the on-request ONNX
int8 build does not meaningfully recover this (0.759, effectively the same
as int4e). If English dominates your traffic, toxic-en is the better
choice: it is not subject to this multilingual-model quantization pattern
and scores higher on English at every comparable size.
7. Not yet measured: latency, on any platform, for this v0.2.0 release. No phone-class or desktop-class timing has been re-run against the v2 artifacts; do not assume the prior generation's numbers carry over, since the vocabulary, quantization scheme and shipped file sizes all changed.
The pattern behind 1-3: counter-speech, negated hate, reclaimed slurs and positive or neutral identity mentions are the dominant false-positive sources in every language; bare slurs are among the harder misses. If your application is FPR-sensitive, use the higher per-language thresholds above and route positives to review instead of auto-actioning.
Per-language deep-dive (real-MHC, flagship LiteRT int4b32 artifact)
HateCheck groups its test cases into named "functionalities" (cells), each
isolating one linguistic pattern. Cells ending _h are hateful (an error is
a miss); cells ending _nh are non-hateful (an error is a false positive).
| lang | strongest cells (acc) | weakest cells (acc) |
|---|---|---|
| en | profanity_nh 0.980, threat_norm_h 0.971, target_obj_nh 0.969 | phrase_question_h 0.521, derog_impl_h 0.536, spell_char_del_h 0.579 |
| de | ref_subs_clause_h 0.979, spell_char_swap_h 0.971, threat_dir_h 0.971 | counter_ref_nh 0.471, ident_pos_nh 0.657, phrase_question_h 0.700 |
| fr | threat_dir_h 1.000, spell_space_add_h 0.994, spell_char_swap_h 0.993 | ref_subs_sent_h 0.496, derog_impl_h 0.629, target_indiv_nh 0.631 |
| nl | threat_dir_h 1.000, counter_quote_nh 0.976, counter_ref_nh 0.929 | phrase_question_h 0.471, ref_subs_sent_h 0.571, derog_impl_h 0.629 |
| pt | threat_dir_h 0.993, threat_norm_h 0.936, profanity_h 0.921 | target_group_nh 0.554, phrase_question_h 0.586, target_obj_nh 0.600 |
| es | threat_dir_h 1.000, spell_char_swap_h 0.957, threat_norm_h 0.952 | target_obj_nh 0.585, target_indiv_nh 0.600, target_group_nh 0.615 |
| it | threat_dir_h 1.000, spell_space_add_h 0.981, threat_norm_h 0.971 | phrase_question_h 0.486, target_group_nh 0.538, ref_subs_sent_h 0.564 |
| pl | threat_dir_h 1.000, counter_quote_nh 0.977, threat_norm_h 0.943 | negate_neg_nh 0.579, target_indiv_nh 0.585, derog_impl_h 0.586 |
Read: explicit threats are near-perfect in every language; errors concentrate
in exactly the trap families described in the failure modes above. Source:
reports/v2ship_tflite_int4b32.md (per-functionality breakdown for all 23
languages).
Fairness across protected groups
Measured on the held-out natural (non-synthetic) test split, threshold 0.40.
Does it recognize hate equally, whoever the target is? On the held-out test rows with human-annotated target groups (Measuring Hate Speech corpus), HATEFUL recall per protected ground:
| ground | n | HATEFUL recall |
|---|---|---|
| AGE | 21 | 1.000 |
| DISABILITY | 27 | 0.889 |
| SEXUAL_ORIENTATION | 116 | 0.853 |
| GENDER | 227 | 0.806 |
| RELIGION | 84 | 0.809 |
| DESCENT | 116 | 0.750 |
| RACE | 186 | 0.753 |
Spread is 25 points (0.750 to 1.000), wider than we would like: RACE and DESCENT recall trail GENDER, RELIGION, SEXUAL_ORIENTATION and DISABILITY by 5-15 points. AGE's 1.000 is on the smallest group (n=21) and should be read with that in mind. This is a monitored target for the next data iteration.
Source: reports/E24_extended.md. Note: the mention-trap style ranking
analysis (BPSN/BNSP AUCs, published for the prior 8-language release) has
not yet been re-run against this v2 model; we are not carrying over the
prior generation's BPSN/BNSP numbers here because they do not measure this
model. That analysis is planned for the next iteration rather than
reported now.
How these numbers were made
The evaluation discipline is the product as much as the weights are:
- Every quality number is re-measured on the exact shipped bytes, not extrapolated from the training checkpoint. Quantized artifacts are re-run through the full scorer per platform (Core ML, LiteRT, ONNX, torch).
- Provenance is tracked per language and never blurred. Real Multilingual HateCheck (8 languages) is the only source for the headline; the 15 translate-and-audit synthetic languages are reported separately, with Lithuanian, Latvian and Estonian further caveated as provisional.
- 444 rows in the synthetic sets are unverified and still counted. The
translate-and-audit pass flags rows it could not confirm, most often an
English slur that survived translation verbatim, so the row may be testing
the wrong construct in that language. They range from 10 rows (
ga) to 60 (da), 0.81% of the synthetic total. They are counted rather than dropped, because "needs a native check" means uncertain and not wrong, and quietly removing uncertain rows biases a score toward whatever is left. The 8 real-MHC languages, including every headline number, contain none of them. - Benchmarks are eval-only, with one honest qualification. No HateCheck row
appears in training, in any language: that is enforced mechanically by a
hash-intersection check over every generated file, and the natural-corpus
test split is carved by content hash before training and never touched.
But HateCheck is a development set for this corpus at the construct level,
and pretending otherwise would be misleading. The template generator mirrors
HateCheck's functional cell names, the clean-side generator targets its
non-hate cells by name, and the per-language register hints were written by
reading its slur cells. So "no verbatim overlap" is what we verify, and
"held out" is not what we can claim. Numbers on real in-the-wild corpora
(
textdetox,offenseval2020) are the ones to weigh if you want a figure untouched by that dependency. - Competitors run through the identical harness, same scorer, same threshold discipline, with their quantized and full-precision builds both measured rather than assumed.
License
Desert Ant Labs Source-Available License. Free for most apps; a commercial license is required at scale. Full terms at the link. Licensing: licensing@desertant.com.
Built exclusively from commercially clean components (CC0 / CC-BY / MIT /
Apache-2.0 training data, MIT base encoder). Attributions, dataset citations
and generator credits are in THIRD_PARTY_NOTICES.md.
- Downloads last month
- 55
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js