UADetox

Rewrites toxic Ukrainian comments into polite literary Ukrainian, keeping the author's point. A fine-tuned Gemma 4 E2B, 1.84 GB, that runs on a CPU, with a small classifier in front that decides what needs rewriting.

Gemma 4 E2B · LoRA · Q4_0 GGUF · llama.cpp · Ukrainian

Examples

Outputs of the deployed model with greedy decoding, not selected. The inputs were written for this page.

CommentRewrite
Ти дебіл, закрий ротаТи, добродію, помиляєшся, тож помовч-но краще.
Та йди ти нахуй зі своїми порадамиТа йди собі, чоловіче добрий, своєю дорогою зі своїми порадами.
Що за довбойоби пишуть такі новини, хоч би перевіряли щосьЩо ж то за новини пишуть такі люди, добродію? Хоч би перевіряли щось, а не вірили на слово.
Знову ці клоуни в раді щось вигадали, задовбали вжеЗнову ті панове в раді щось вигадали, та вже, по щирості, несила терпіти.
Пиздець, а не дороги, мер тільки бабло пиляєОй лишенько, а не дороги, а мер, бач, тільки гроші розкрадає!
Купив цей телефон, гівно повне, батарея здохла за тижденьКупив я цей телефон, та й нічого з нього не вийшло: батарея, бач, за тиждень і згасла.
Дякую за новину, дуже корисноNot rewritten. Classifier score 0.01, threshold 0.21.

The criticism stays. The mayor is still accused and the phone is still bad; profanity and insults are removed.

Benchmark

A rewrite is counted as good only if it keeps the meaning and stance, is fluent, has no toxicity left and is in the literary register. Verdicts are from an LLM judge (DeepSeek V4.1 Flash, fixed provider); intervals are 95% paired bootstrap.

300 comments from the development split.

SystemParamsGood, %Toxicity left, %Meaning lost, %
Reference rewrites–91.60.021.2
Gemma 4 31B, 4-shot31B74.73.332.7
Gemma 4 26B-A4B, 4-shot26B68.73.742.3
UADetox E2B2B eff.57.34.742.7
Gemma 4 E2B, 4-shot2B eff.20.544.159.3

Differences from UADetox E2B in good rate: 31B +17.3 [+10.0, +25.0], 26B +11.3 [+3.7, +18.3], reference +34.7 [+28.3, +41.1]. On the full development split (1,966 comments) the fine-tuned model scores 59.7 and the on-device build 59.5.

On-device build

StepSizeGood, %Effect
LoRA merged into Gemma's Q4_0 QAT weights3.35 GB–4.8 [–6.9, –2.7]Naive merge undoes the quantization-aware training
LoRA trained through the Q4_0 rounding3.35 GB–1.4 [–3.6, +0.7]Within noise of full precision
Vocabulary trimmed 262k → 88k tokens1.84 GB+0.0584–87% of outputs byte-identical

Effects are changes in good rate: the two merges against the same model in bf16, the trim against the untrimmed build. The trim keeps Ukrainian, punctuation and the 40k most frequent Latin tokens, closed under BPE merges, including the per-layer embedding tables.

CPU serving

llama.cpp, Q4_0, one request at a time, about 74 prompt and 70 output tokens per comment.

MachineGeneration, tok/sPer comment, sSustained, comments/min
Hetzner CAX21, 4 ARM cores, 8 GB20.44.222–31
Hetzner CX23, 2 shared x86 cores, 4 GB6.212.4~8

The classifier (multilingual-e5-small, ONNX) handles about 20 comments/s on one core. About 30% of comments in public Telegram threads are flagged, so a CAX21 keeps up with roughly 80 comments per minute.

Usage

# serve; set -t to the number of cores
llama-server -m uadetox_e2b_distill_q4_trim_q4_0.gguf \
  -c 1024 -np 1 -t 4 -fa on --no-cache-prompt \
  --chat-template-kwargs '{"enable_thinking":false}'

# the comment is the whole prompt; decode greedily
curl -s localhost:8080/completion -d '{
  "prompt": "<|turn>user\nТи дебіл, закрий рота<turn|>\n<|turn>model\n",
  "n_predict": 256, "temperature": 0, "top_k": 1,
  "stop": ["<turn|>", "<|turn>"]
}' | jq -r .content

No system prompt or instructions. This is the format the model was trained and evaluated with.

Limitations

Citation

@misc{uadetox2026,
  title  = {UADetox: Polite Literary Rewriting of Toxic Ukrainian Comments on Device},
  author = {{UADetox project}},
  year   = {2026},
  url    = {https://uadetox.pages.dev}
}