Turns toxic Ukrainian comments into polite, literary Ukrainian — keeping what the author meant, dropping the insult. A 1.8 GB model that runs on a phone-class CPU.
TL;DR. A small classifier decides which comments need rewriting; a fine-tuned Gemma 4 E2B rewrites them in an old-fashioned, courteous literary register — «добродію» instead of a slur. To fit a phone we trained the adapter against the exact 4-bit weights it will run on and trimmed the vocabulary to Ukrainian and Latin tokens: 3.35 → 1.84 GB, ~4 s per comment on 4 ARM cores.
Examples
Real outputs of the deployed model (greedy decoding, no cherry-picking among samples). The inputs were written for this page; score = gate probability that the comment needs a rewrite (threshold 0.21).
Ти дебіл, закрий рота“You're a moron, shut your mouth”gate 0.98
→
Ти, добродію, помиляєшся, тож помовч-но краще.“You are mistaken, good sir, so you'd better keep quiet.”
Та йди ти нахуй зі своїми порадами“F*** off with your advice”gate 0.98
→
Та йди собі, чоловіче добрий, своєю дорогою зі своїми порадами.“Go your own way, my good man, with your advice.”
Що за довбойоби пишуть такі новини, хоч би перевіряли щось“What kind of idiots write news like this, they could at least check something”gate 0.98
→
Що ж то за новини пишуть такі люди, добродію? Хоч би перевіряли щось, а не вірили на слово.“What sort of news do such people write, sir? They could at least check, not take it on faith.”
Знову ці клоуни в раді щось вигадали, задовбали вже“These clowns in parliament made something up again, sick of them”gate 0.97
→
Знову ті панове в раді щось вигадали, та вже, по щирості, несила терпіти.“Those gentlemen in parliament came up with something again, and honestly, it is past bearing.”
Пиздець, а не дороги, мер тільки бабло пиляє“The roads are f***ed, the mayor just skims the cash”gate 0.98
→
Ой лишенько, а не дороги, а мер, бач, тільки гроші розкрадає!“Oh woe, call these roads? And the mayor, look, only embezzles the money!”
Купив цей телефон, гівно повне, батарея здохла за тиждень“Bought this phone, total crap, battery died in a week”gate 0.95
→
Купив я цей телефон, та й нічого з нього не вийшло: батарея, бач, за тиждень і згасла.“I bought this phone, and nothing came of it: the battery, see, faded within a week.”
Дякую за новину, дуже корисно“Thanks for the news, very useful”gate 0.01
→
left as is — the gate does not call the rewriter
Note the criticism survives: the mayor is still accused, the phone is still bad. The model removes insults and profanity, not opinions.
How it works
Commentany Ukrainian text, ≤ 1,000 chars
→
Gatefine-tuned multilingual-e5-small, 118M params, ~20 comments/s on one CPU thread
Polite replysame meaning and stance, literary register
Data
About 52k toxic Ukrainian comments (mostly public Telegram channels), each paired with a neutral and a literary rewrite. Rewrites were generated by large LLMs, judged, sent back for rework when they failed, and audited by hand; splits are grouped by thread so that no conversation leaks between train and test.
Training
Supervised LoRA fine-tuning of Gemma 4 E2B on the literary rewrites, with the bare comment as the prompt (no system prompt — what the phone sees is what was trained).
Q4_0-aware training. Merging a LoRA into Google's 4-bit QAT weights and re-quantising lost 4.8 points of judged quality. Training the adapter through the exact Q4_0 rounding of the merged weights keeps the merge within 1.4 points of full precision (not significant).
Vocabulary trim. Gemma's 262k-token vocabulary, including the per-layer embedding tables, was cut to 88k tokens (Ukrainian, punctuation and the 40k most frequent Latin tokens, closed under BPE merges): 3.35 → 1.84 GB, CPU generation 19 → 26 tokens/s, 84–87% of outputs byte-identical and no detectable quality change on the rest.
Results
A rewrite counts as good only if it keeps the meaning and stance, is fluent Ukrainian, has no toxicity left, and is in the literary register. Verdicts come from an LLM judge (DeepSeek V4.1 Flash, pinned provider) with paired bootstrap confidence intervals.
System (300 dev comments)
Size
Good %
Toxicity left %
Meaning lost %
Reference rewrites (gold)
—
91.6
0.0
21.2
Gemma 4 31B, untuned, 4-shot
31B
74.7
3.3
32.7
Gemma 4 26B-A4B, untuned, 4-shot
26B
68.7
3.7
42.3
UADetox E2B (fine-tuned)
2B eff.
57.3
4.7
42.7
Gemma 4 E2B, untuned, 4-shot
2B eff.
20.5
44.1
59.3
On all 1,966 dev comments the fine-tuned model scores 59.7% good and the on-device build (Q4_0 merge, trimmed vocabulary) 59.5%. Fine-tuning moves the small model from 20% to ~58% good; the untuned 31B is still 17 points ahead — the price of running on a phone.
Serving on a CPU
1.84 GB
single-file GGUF
20 tok/s
generation, 4 ARM cores (Hetzner CAX21)
4.2 s
per rewrite, ~70 output tokens
~25 / min
sustained rewrites, no errors at 8 concurrent clients
Quickstart
Weights. The GGUF and the gate are being prepared for public release; until then the model is available on request, and the Telegram bot is the easiest way to try it.
# 1. serve the model (CPU; use as many threads as you have 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}'
# 2. rewrite a comment — the bare comment is the whole prompt
curl -s http://127.0.0.1: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
# → Ти, добродію, помиляєшся, тож помовч-но краще.
Use exactly this prompt and greedy decoding: it is what the model was trained and evaluated on. No system prompt, no instructions.
# gate + rewriter behind one endpoint (FastAPI; see scripts/serving/)
curl -s http://127.0.0.1:8000/v1/rewrite \
-H "Authorization: Bearer $UADX_API_KEY" -H "Content-Type: application/json" \
-d '{"text": ["Ти дебіл, закрий рота", "Дякую за новину"], "mode": "auto"}'
{"results": [
{"needs_rewrite": true, "score": 0.9768, "rewrite": "Ти, добродію, помиляєшся, тож помовч-но краще."},
{"needs_rewrite": false, "score": 0.0063, "rewrite": null}],
"gate": "uadetox-gate-e5-small", "threshold": 0.208}
# classify only (no generation): POST /v1/classify {"input": ["..."]}
# always rewrite (skip the gate): "mode": "always"
Modelled on task APIs such as translation and moderation endpoints rather than chat: one request, one result, a result cache and back-pressure (HTTP 429) built in.
Direct chat: send @uadetoxdevbot any comment — it replies with a rewrite and 👍 / 👎 buttons. Your ratings help improve the next version.
Any chat (inline): type @uadetoxdevbot followed by your comment, then tap the suggestion to send the polite version instead.
Groups: add the bot; it replies with a rewrite only to messages the gate flags. Admins: /uadetox_off@uadetoxdevbot, /uadetox_on@uadetoxdevbot.
Data: messages the bot processes and its rewrites are stored without names or user ids (salted hashes only) to improve the model. Don't send personal data.
Limitations
Roughly 6 in 10 rewrites are fully good by the judge's strict standard. The most common failure is losing part of the meaning (~40% of outputs drop or blur some detail), then fluency slips.
Long and unusual comments are harder: quality falls for comments over 40 words and for sexual or threatening content.
The register is deliberate: «добродію», «бач», «по щирості» — archaic courtesy, which can read as ironic.
The gate is a policy model, not a fact checker. It catches profanity and slurs well; veiled mockery and insults without swear words are caught far less often.
Evaluation caveats: numbers are on the development split with one LLM judge; the judge is stricter than our own human labels. A locked test-set run and a second human rater are planned before the paper.
Citation
@misc{uadetox2026,
title = {UADetox: Polite Literary Rewriting of Toxic Ukrainian Comments on Device},
author = {{UADetox project}},
year = {2026},
note = {https://uadetox.pages.dev}
}