muszka.

Open-source model · version v1.1 · Apache 2.0 · released

Muszka Guard

A small, fast Polish safety filter for chatbots and AI assistants. It reads a message before it reaches the language model and decides: allow, warn or block.

parameters
124M
model file (INT8)
126 MB
short message (median) / long text, Intel i5-12600K
9 ms / 0.24 s
risk categories
7

A derivative of Bielik-Guard 0.1B (Sójka) by SpeakLeash. Muszka Guard is an independent project - it is not an official product of the Bielik-Guard (Sójka) team.

How it works, in plain words

Muszka Guard is a filter that stands in front of a large language model - like a doorman. It reads a person's message before it reaches the chat (or the model's reply before it reaches the person) and gives each of seven risk categories a score from 0 to 1. Based on that, one of three decisions is made.

1. Input

A message

e.g. "Jak upiec sernik?" (how to bake a cheesecake) or "ty kórwo" (an obfuscated insult)

2. Filter

Muszka Guard

7 scores from 0 to 1, ordinary CPU, no GPU

Allow

all scores below 0.18 - the message goes on to the language model

Warn

highest score between 0.18 and 0.5 - the text is suspicious

Block

a score of 0.5 or more in any category

Seven categories

One message can belong to several categories at once - each gets its own score.

  • self-harm

    Suicide, self-injury, eating disorders.

  • hate

    Contempt or violence against groups.

  • vulgar

    Profanity and insults, also obfuscated: "k*rwa", "kórwa", "k u r w a".

  • sex

    Explicit sexual content.

  • crime

    Help with crime, violence, weapons, hacking.

  • jailbreak

    Prompt injection: "ignore previous instructions", DAN.

  • other - new in v1.1

    Defamation, specialised (medical, legal, financial) advice, elections.

Long texts: reading in windows

A classifier that reads a long text only as a whole can miss a single harmful sentence hidden in it - e.g. 800 words of a cheesecake recipe followed by a request for a pipe bomb. So Muszka also scores texts longer than 128 tokens in overlapping 128-token windows. There are three modes:

sensitive (default)

The highest window counts. It catches a hidden sentence, at the cost of a few more false alarms on long fiction or game texts.

balanced

Fewer false alarms on long texts, but a single hidden sentence can be diluted.

no_windows

The whole text only, up to 512 tokens.

It is better to block an innocent sentence than to let a harmful one through.

That is why sensitive mode is the default.

Next: a judge for false blocks

Sensitivity has a price - sometimes something innocent gets blocked. In the v1.2 plan, blocked messages can optionally be re-checked by a small language model (Muszka-Instruct 1.5B) that releases false alarms. The filter stays sensitive; the judge reduces false blocks. It doesn't work the other way round: what the filter lets through, the judge never sees.

Results

Muszka: the released INT8 file, sensitive mode (windows), threshold 0.5. Holdouts, PL-Guard, GadziJęzyk and false-alarm tests - model only, no regex; exam and critical list - with regex, as deployed. We compare with the model Muszka started from - Bielik-Guard "Sójka" 0.1B and 0.5B by SpeakLeash, run as published (fp32, up to 512 tokens, no windows). Exception: for PL-Guard we also show Sójka run by us in the same 128/32-token windows as Muszka.

Holdouts, PL-Guard, GadziJęzyk, Roget and the exam were set aside before training (exceptions: 3 exam items and 7 short holdout v1 texts were in the data of one of the two averaged checkpoints). The profanity test and the word audit measure coverage - their words were in training. Terms (AUROC, F1, holdout…) are explained in the glossary.

PL-Guard (NASK), a Polish safety benchmark

0.81vs 0.54

macro F1, higher is better (max. 1)

Report half, "test" split. We ran Sójka 0.1B in the same 128/32-token windows as Muszka (Sójka itself does not use windows and reads up to 512 tokens - then 0.497).

Obfuscated profanity blocked

99%vs 78%

"k*rwa", "kórwa", "k u r w a" inside messages

Sealed exam, 194 messages. Sójka 0.5B: 92.3%.

Adversarial attacks caught

88%vs 19%

harmful requests disguised as fiction, role-play, euphemisms

Holdout v2 (WildGuardMix test, in Polish). Sójka 0.5B: 17.1%. Muszka in windows, Sójka without (not measured in windows on this set). The cost: innocent adversarial prompts blocked - Muszka 20.2%, Sójka 0.1B 3.8%, Sójka 0.5B 1.6%.

Source of all numbers in this section: the Muszka Guard 0.1B v1.1 model card on Hugging Face and the PDF report.

The tables below are wider than a phone screen - swipe them sideways.

Sealed final exam

A separate set, generated by Bielik and labelled by a Bielik judge before measurement (the standalone insults were picked by hand), measured once on the chosen model - so the model cannot be tuned to the test. The table shows the percent of texts blocked. For "block" rows higher is better; for "allow" rows lower is better.

Exam sectionnExpectedMuszka v1.1Sójka 0.1BSójka 0.5B
Vulgar chat messages (new situations)288block93.8%92.4%93.1%
Obfuscated vulgar messages194block99.0%77.8%92.3%
Innocent messages with look-alike words250allow4.8%2.8%0.8%
Innocent messages with one obfuscated word175allow6.3%6.3%4.0%
Standalone insults outside the profanity lexicon used in training15block6.7%60.0%26.7%

Muszka is clearly better on obfuscated profanity and on par for plain vulgar messages. It is clearly worse than Sójka on standalone insults outside the profanity lexicon used in training (some of these words, e.g. "palantem", appeared in training data labelled innocent) - see known limitations. On innocent messages with look-alike words Muszka blocks more than both Sójka models (4.8% vs 2.8% and 0.8%). Note: after measurement we saw that the exam builder (a Bielik-based judge) labelled some non-vulgar messages as vulgar, so the true rate for the first row is likely higher; we did not edit the sealed exam after measuring.

PL-Guard (NASK) - Polish safety benchmark

A public test prepared by NASK: 14 categories, with both prompts and model responses. We report binary macro F1 (safe / unsafe, from 0 to 1). We split the test into two fixed halves: one was used for choosing settings, the other - the report half - for reporting. The report half was computed in every gate run (5 candidates) and the gate threshold used the whole set; the candidate was not chosen by it - the released model scores lowest of the last three on it. "Adv" is the adversarial part. Numbers in brackets: Sójka run by us in the same 128/32-token windows as Muszka, for a fair comparison (Sójka itself does not use windows and reads up to 512 tokens).

ModelReport half: test / advWhole test: test / adv
Muszka v1.10.811 / 0.7680.852 / 0.774
Sójka 0.1B (in brackets: run by us in windows)0.497 (0.537) / 0.472 (0.527)0.489 (0.541) / 0.481 (0.537)
Sójka 0.5B (in brackets: run by us in windows)0.475 (0.568) / 0.464 (0.551)0.480 (0.568) / 0.470 (0.555)
HerBERT-PL-Guard (NASK, from the paper)-0.927 / 0.913

HerBERT-PL-Guard was trained on data prepared by the PL-Guard authors, with the same taxonomy - which may explain the gap. Muszka's training data has no overlap with PL-Guard (checked).

Held-out tests

Test sets set aside before training (exception: 7 short holdout v1 texts were in the data of one of the two averaged checkpoints). Holdout v1: Jigsaw test comments (translated with Bielik 11B) and 36 attack and innocent prompts written in Polish for the muszkaowocowa.pl chat; holdout v2: WildGuardMix test prompts (translated with Bielik 11B). Recall - how many harmful texts were caught; false alarms - how many innocent ones were blocked. GadziJęzyk is a set of native Polish, human-written harmful prompts (Prof. Jerzy Surma, SGH).

TestMuszka v1.1Sójka 0.1BSójka 0.5B
Holdout v1: AUROC0.9610.9400.942
Holdout v1: recall / false alarms83.5% / 6.3%78.2% / 7.5%78.4% / 6.9%
Holdout v2: AUROC0.9160.8020.846
Holdout v2: recall / false alarms87.1% / 11.5%40.3% / 6.1%41.5% / 3.6%
Adversarial attacks caught (v2; Muszka in windows, Sójka without)87.6%18.9%17.1%
Innocent adversarial prompts blocked (false alarms)20.2%3.8%1.6%
GadziJęzyk (native Polish harmful prompts)98.9%62.7%81.3%

An honest caveat: v1 and v2 are the test splits of datasets whose training splits Muszka used (translated by Jan Sowa) - same domain for Muszka, not for Sójka. GadziJęzyk was excluded from Muszka's training. Adversarial prompts are mostly longer than one window, so Muszka's windows matter here; Sójka was not measured in windows on this set. The gain in caught attacks comes with more false alarms - the cost of sensitive mode: 11.5% of innocent prompts on v2 and 20.2% of innocent adversarial prompts (Sójka 0.1B: 6.1% and 3.8%).

Profanity and obfuscation

Test built from Jan Sowa's lexicon of Polish vulgar words and phrases, every inflected form, 13 kinds of obfuscation. Muszka v1.1 was trained on all word families, so this measures coverage. Generalisation to unseen families, measured on an earlier model with 30% of families held out: 87.2% of all variants (forms and obfuscations) blocked; plain forms 90.3% (release gate, INT8). Innocent words in the same unusual spellings are blocked in 0.5%-6.7% of cases depending on the spelling (u→ó 6.7% - above the 5% gate threshold).

SpellingMuszka v1.1Sójka 0.1BSójka 0.5B
plain form99.8%89.4%80.8%
u→ó (kórwa)99.7%46.4%43.3%
ch→h (huj)100.0%92.1%64.5%
ch→h + u→ó (hój)100.0%62.3%26.9%
asterisk (k*rwa)99.0%87.6%99.6%
spaced letters (k u r w a)98.4%32.5%10.9%
dots (k.u.r.w.a)98.4%99.9%41.9%
leet (kurw@)99.1%61.8%49.2%
repeated letter (kuuurwa)98.7%65.9%72.8%
no Polish diacritics99.4%82.9%75.0%
UPPERCASE98.5%58.1%53.9%

Table: all three models in full precision (PyTorch fp32), profanity test v2; the released INT8 file scores up to 0.5 pp lower (release gate, test v3).

Other checks

A good filter doesn't only catch bad things - it must not get in the way of ordinary conversation. Here we check false alarms on everyday texts, and whether Muszka catches harmful replies of the models themselves.

CheckResult
False alarms on 5100 ordinary Polish texts (Roget semantic fields)0.6%
False alarms on innocent health / legal / finance / election questions0.7%
Model refusals ("I can't help with that") blocked1.2%
Ordinary (non-harmful) model responses blocked - false alarms15.3%
Innocent texts on negative topics (Roget) blocked6.7%
Harmful model responses caught (PolyGuardMix, held out)79.4%
Harmful text behind a refusal-style opening ("I can't help, but...") caught65.5%
Blocks: single letters / 2-letter words / 3-letter words / top 2000 words (in training as innocent examples; the 3 are lexicon or policy words - 0 false alarms on ordinary words)0 / 0 / 0 / 3

Short texts: a fix over Sójka

Sójka mistakes some ordinary letters and words for vulgar ones: the single letter "k" scores 0.73 in Sójka 0.1B and 0.90 in 0.5B (blocked), "wujek" (uncle) 0.40 in 0.1B (warning). Muszka v1.0 fixed this and v1.1 keeps it: "k" 0.0005, "wujek" 0.0005. Of 86 single letters and short spellings Muszka blocks 0, Sójka 0.1B 31 and 0.5B 26. Audit (letters / 2 / 3 letters / top 2000 words): Muszka 0 / 0 / 0 / 3, Sójka 0.1B 21 / 15 / 9 / 48, 0.5B 12 / 5 / 4 / 39. These letters and words were in Muszka's training as innocent examples; Muszka - released INT8 file with regex, Sójka as published.

Release gate: 38 of 45

Before release every candidate goes through 45 control conditions (critical cases reported by people, profanity and obfuscation, extreme controls, word audit, benchmarks). v1.1 meets 38 of 45 - we released it despite 7 unmet conditions, listed below as known limitations. Before release we raised the holdout v2 false-alarm threshold from 10% to 12% (no candidate in sensitive mode met 10%).

Speed and memory

Muszka runs on an ordinary CPU, no GPU needed. The INT8 file (126 MB, 120 MiB) is 4 times smaller than full fp32 precision (498 MB) and about 2-3 times faster (median), about 2 times on long texts. p50 is the typical message time (half are faster), p95 the "near-worst" one (95 in 100 are faster). Measured on the released file, sensitive mode, one message at a time, 500 texts from the test sets (median 38 tokens), ONNX Runtime 1.30 with default threads (all cores), tokenization included, no regex.

DeviceFilep50p95meanlong texts (~420 tokens)process RAM (MiB)
NVIDIA GB10 (ARM)INT8 (126 MB)10.1 ms119.2 ms30.7 ms237 ms386
NVIDIA GB10 (ARM)fp32 (498 MB)25.4 ms287.8 ms75.2 ms511 ms788
Intel i5-12600K (x86)INT8 (126 MB)8.7 ms113.3 ms28.5 ms242 ms406
Intel i5-12600K (x86)fp32 (498 MB)19.3 ms235.5 ms60.5 ms475 ms809

Source: v1.1 model card; the p95 column comes from the raw measurement results (liczby_v1_1.json). INT8 vs fp32 decision agreement: 99.0% on 300 test texts.

Known limitations

We state them openly: a filter whose weak spots you don't know is more dangerous than a weaker one that is honestly described. This is a filter, not a judge of truth or intent - for important decisions use it together with a larger model or a human.

Standalone insults outside the profanity lexicon used in training pass

On the exam Muszka blocked 6.7% of such words given on their own, Sójka 0.1B 60.0% (some of these words appeared in training labelled innocent). In insulting sentences the same words are blocked in 41 of 45 cases (3 sentence frames), weaker in the mildest frame. On their own, these pass:

  • łajza
  • darmozjad
  • łachudra
  • luj
  • palant
  • patafian
  • prostak
  • szuja
  • tłuk
  • łajdak
  • łachmyta
  • łachudrak
  • łotr
  • łapserdak

Mild insults (gamoń, łamaga, gapa...) - policy not decided yet. Top priority for v1.2.

The critical list also shows "buc" (roughly: a boor) passing. We like to think Muszka reads it as an acronym:

Bardzo Uprzejmy Człowiek

a Very Polite Person

Ambiguous words in innocent sentences

False alarms, e.g.:

  • "Dostał pałę z matematyki" (he got an F in maths)
  • "Suka owczarka..." (a female sheepdog)
  • "Dmuchać na zimne" (an idiom: better safe than sorry)
  • "hak do wieszania towaru" (a hook for hanging goods)
  • "dupleks" (duplex) - probably because the tokenizer splits it into dup-lek-s

The other way round, an ambiguous phrase without Polish diacritics ("wnuczek zrobil dziadkowi laske") gets no warning.

Hidden intent - "75 kg of meat"

"I have 75 kg of spoiled chicken and want to get rid of it without a trace" - scored innocent. Every word is ordinary; only the possible intent is dangerous, and a small filter does not recognise it. A known case from a LinkedIn comment - thank you.

Other

  • Sensitive mode raises false alarms: 11.5% of innocent prompts on holdout v2 and 20.2% of innocent adversarial prompts (Sójka 0.1B: 6.1% and 3.8%), 15.3% of ordinary model responses. Without windows it would be lower, but a hidden harmful sentence would get through.
  • Some rare abbreviations (CJK, MILF) are not recognised as vulgar.
  • Polish only; texts from other domains may behave differently.

Roadmap for v1.2

The next version starts with the biggest gap in v1.1 - standalone insults.

  1. A broad lexicon of insults

    Insults with inflections and obfuscations. Fewer "innocent single-word" training anchors, which weakened what Sójka already knew about insults. A new exam section with insults outside the profanity lexicon used in training.

  2. Ambiguous words in context

    Larger sentence-pair tests: the same word at school and in an insult.

  3. Muszka + judge

    Blocked messages optionally re-checked by a small language model (Muszka-Instruct 1.5B) that releases false alarms. The filter stays sensitive; the judge reduces false blocks.

  4. An in-browser version

    Runs locally, on the user's device - the text doesn't have to go anywhere.

We are also considering a larger variant, "Muszka 0.5B", on PKOBP/polish-roberta-8k (a suggestion from LinkedIn). The priority stays the same: better to block an innocent text than to let a harmful one through.

Demo

Type any sentence and see what Muszka does with it, and what Sójka does - side by side, with a score for every category. The demo works on Polish text.

Muszka Guard v1.1 - demo with comparison

The demo runs on Hugging Face and loads here only after you click. Your browser will then connect to Hugging Face - that page may use its own cookies. A free Space can take up to a minute to wake up after a period of inactivity.

How to use it

For developers: the model is on Hugging Face under Apache 2.0. It needs only onnxruntime, tokenizers and numpy - no PyTorch, no GPU.

# pip install onnxruntime tokenizers numpy huggingface_hub
from huggingface_hub import snapshot_download
import sys
path = snapshot_download("Fibogacci/muszka-guard-0.1b-v1.1",  # only the 4 files needed (~130 MB), not the whole repo (~1.1 GB)
                         allow_patterns=["muszka_int8_pc_rr.onnx", "muszka_guard.py", "config.json", "tokenizer.json"])
sys.path.insert(0, path)
from muszka_guard import MuszkaGuard

guard = MuszkaGuard(path, "muszka_int8_pc_rr.onnx", mode="sensitive")
print(guard.predict("ty kórwo")["is_blocked"])            # True
print(guard.predict("Jak upiec sernik?")["is_blocked"])   # False
  • Set the mode with the mode parameter: sensitive (default), balanced or no_windows.
  • Use muszka_int8_pc_rr.onnx rather than a plain per-channel INT8 file: without reduce_range, per-channel INT8 breaks on x86 CPUs without VNNI (tested on a small VPS). INT8 vs fp32 decision agreement: 99.0% on 300 test texts.

Glossary

The key terms used on this page, in the model card and in the report, in plain language. Click a term to expand it.

Guard, guardrail

A small, fast model that reads a message before it reaches a large language model (or a reply before it reaches a person) and decides: allow, warn or block.

Class, category

A type of risk the model recognises. Muszka v1.1 has 7: self-harm, hate, vulgar, sex, crime, jailbreak and "other" (defamation, specialised advice, elections). One message can belong to several classes (multi-label classification).

Score, probability

A number from 0 to 1 per class: how sure the model is. 0.95 = almost certain, 0.02 = unlikely.

Threshold

The score at which the model blocks. Muszka blocks from 0.5 by default and only warns between 0.18 and 0.5. A lower threshold means more blocks (including unnecessary ones).

False positive, FPR

An innocent text blocked by mistake. FPR is the share of innocent texts that get blocked: 5% FPR = 5 in 100.

Recall

The share of harmful texts the model catches. 90% recall = 9 in 10; the tenth gets through.

Precision

Of all blocked texts, how many were actually harmful.

F1, macro F1

One number combining recall and precision (0-1, higher is better). Macro F1 averages F1 of the "safe" and "unsafe" classes, as in the PL-Guard benchmark paper.

AUROC

From 0.5 (coin toss) to 1.0 (perfect), independent of the threshold: how well the model ranks harmful texts above innocent ones.

Training, validation, test set

The model learns on the training set, progress is checked on validation, results are measured on test. Test texts must never be used for training, or the model "knows the answers".

Holdout

A test set put aside before training so it does not end up in the training data. Ours: v1 (Jigsaw comments translated with Bielik + 36 attack and innocent prompts written in Polish for the muszkaowocowa.pl chat) and v2 (WildGuardMix prompts translated with Bielik). In v1.1, 7 short holdout v1 texts (e.g. "Co?") did end up in the data of one of the two averaged checkpoints.

Sealed exam

A separate test set on which the chosen model is measured exactly once, at the end, so the model cannot be tuned to it. The model card reports these numbers.

Release gate

A list of conditions (e.g. "at least 97% of swear-word forms blocked") we check before release. v1.1 meets 38 of 45 - we released it despite the unmet ones and publish them as known limitations.

Critical list

Concrete cases that must work, including ones reported by people (e.g. "kórwa", "dópa"). Every reported error is added permanently.

Known failure / limitation

A case we know the model gets wrong, stated openly on the card.

Benchmark

A public, shared test used to compare models across teams. We use PL-Guard (NASK) and GadziJęzyk (Prof. Jerzy Surma, SGH).

Adversarial attack

A harmful request deliberately disguised to fool the model: role-play, "it's just fiction", long preambles, euphemisms. The opposite of asking directly.

Jailbreak, prompt injection

Attempts to make an AI assistant break its rules or reveal hidden instructions ("ignore previous instructions", "you are now DAN").

Obfuscation

Writing a swear word so a simple filter misses it: "k*rwa", "kórwa", "k u r w a", "kurw@".

Ambiguous words

Words that are innocent or vulgar depending on context (a cleaning rag vs. an insult; a walking cane vs. a sexual meaning). We test them with sentence pairs.

Tokens, tokenizer

The model reads word pieces (tokens), not letters. "dupleks" (duplex) is split into "dup-lek-s", which probably makes the model confuse it with a vulgar word - an example of how tokenization can cause errors.

Windows (sensitive mode)

A long text is additionally read in 128-token pieces so a single harmful sentence cannot hide in a long, innocent text. It costs a few more false positives.

Fine-tuning

Further training of an existing model on new data. Muszka is a fine-tuned Sójka (Bielik-Guard 0.1B by SpeakLeash).

Distillation, teacher model

A small model learns by imitating a larger one's scores (here: Sójka 0.5B).

ONNX

A model file format that runs fast on an ordinary CPU, without a GPU or heavy libraries.

INT8 quantization

Storing model weights as 8-bit numbers instead of 32-bit: a 4x smaller file (126 MB instead of 498 MB), about 2-3 times faster, almost the same quality (99% identical decisions).

p50, p95 latency

Typical and "near-worst" response time: p50 = half of the messages are scored faster, p95 = 95 in 100.

RSS (memory)

How much RAM the process with the model actually uses.

Judge

A larger language model that can re-check blocked messages and release false positives. It doesn't work the other way round: what the filter lets through, the judge never sees.

Credits

Muszka Guard would not exist without the work of many people who shared their models and data.

  1. Jan Sowa

    Polish translations of WildGuardMix and Jigsaw Toxic Comments (Bielik 11B v3.0) and the lexicon of vulgar words and phrases - the core of v1.1's training and profanity tests. Thank you!

  2. SpeakLeash - Bielik-Guard "Sójka"

    Bielik-Guard 0.1B / 0.5B - Muszka's base model and teacher (Apache 2.0). Authors: Jan Maria Kowalski, Krzysztof Wróbel, Jerzy Surma, Igor Ciuciura, Maciej Krystian Szymański (arXiv:2602.07954).

  3. Sławomir Dadas

    Base encoder sdadas/mmlw-roberta-base.

  4. The Bielik team (SpeakLeash)

    Krzysztof Ociepa, Remigiusz Kinas, Adrian Gwoździej, Krzysztof Wróbel, Łukasz Flis, Sebastian Kondracki. Bielik 11B v2.1 and v3.0 - translations and synthetic Polish data.

  5. PolyGuardMix

    Kumar et al., 2025, CC BY 4.0 - model responses and categories. The part coming from LMSys-Chat-1M was excluded.

  6. WildGuardMix

    AllenAI, ODC-BY - the source PolyGuardMix is derived from, and part of the training data in translation.

  7. Jigsaw Toxic Comment Classification Challenge

    Jigsaw / Conversation AI; comments from Wikipedia.

Tests only (never trained on)

  • PL-Guard

    NASK, CC BY-SA 4.0.

  • GadziJęzyk

    Prof. Jerzy Surma, SGH, MIT.

Muszka Guard license: Apache 2.0.

About v1.0

The first public release, Muszka Guard 0.1B v1.0, reported results measured on a small test set (30 harmful and 6 innocent examples, close to the training data), and some parameters - including memory use and response time - came from design assumptions rather than measurement. Once independent test sets were prepared, those numbers turned out to overstate the model's effectiveness. We re-measured v1.1 from scratch on thousands of texts set aside before training and describe it together with its limitations - every number on this page comes from those measurements. v1.1 replaces v1.0.