Mateza Marian en↔rw: English ↔ Kinyarwanda translation
One model, both directions. A 61M-parameter Marian Transformer. Two tags at the start of the input, <src_en><tgt_rw> or <src_rw><tgt_en>, pick the direction.
| Quality | 46.52 mean chrF++ on FLORES+ devtest with beam 4, or 46.02 greedy. That is +4.9 over the OPUS-FT v3 models it learned from (41.64). |
| Size | 61M parameters, 242 MB (fp32 safetensors). It is one model for both directions; v3 needed two models of 75M each. |
| Licence | Apache-2.0 for the weights. See Data and attribution for the training data. |
Built by Promesse Rukundo and Mateza Kayondo (Mateza).
Quick start
pip install torch "transformers>=5.0" huggingface_hub
import os, sys
from huggingface_hub import hf_hub_download
sys.path.insert(0, os.path.dirname(hf_hub_download("mateza/mateza-marian-en-rw", "translator.py")))
from translator import Translator
t = Translator("mateza/mateza-marian-en-rw") # GPU (fp16) if available
t.translate("Good morning, how are you?", "en->rw") # 'Mwaramutse, amakuru?'
t.translate(["Murakoze cyane.", "Abana bagomba kujya ku ishuri buri munsi."], "rw->en")
# ['Thank you so much.', 'Children should go to school every day.']
t.translate("Wash your hands with soap.", "en->rw", beams=4) # beam 4: about 0.5 chrF++ better, slower
Command line. Put the direction in quotes, because the shell treats a bare > as a redirect:
python translator.py --repo . --direction "en->rw" "Thank you very much."
python translator.py --repo . --direction "rw->en" --beams 4 "Murakoze cyane."
Plain transformers, without translator.py
The tokenizer does not add the language tags itself, so add them yourself:
import torch
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
repo = "mateza/mateza-marian-en-rw"
tok = AutoTokenizer.from_pretrained(repo)
model = AutoModelForSeq2SeqLM.from_pretrained(repo).eval()
def encode(text, src, tgt):
tags = tok.convert_tokens_to_ids([f"<src_{src}>", f"<tgt_{tgt}>"])
return tags + tok(text, add_special_tokens=False).input_ids + [tok.eos_token_id]
ids = torch.tensor([encode("Thank you very much.", "en", "rw")])
out = model.generate(input_ids=ids, num_beams=1, max_new_tokens=128, no_repeat_ngram_size=3, repetition_penalty=1.1)
print(tok.decode(out[0], skip_special_tokens=True)) # 'Urakoze cyane.'
Only en and rw were trained. The vocabulary also has tags for fr, ar, sw, lg and zh_Hans, which are
reserved for later versions. Don't use them yet.
Evaluation
FLORES+ devtest (1,012 sentences per direction)
| Decoding | Direction | chrF++ | BLEU | OPUS-FT v3 chrF++ |
|---|---|---|---|---|
| greedy | en→rw | 47.07 | 18.49 | 41.49 |
| greedy | rw→en | 44.98 | 20.37 | 40.12 |
| beam 4 | en→rw | 47.60 | 19.00 | 42.60 |
| beam 4 | rw→en | 45.43 | 20.78 | 40.69 |
No empty outputs and no degenerate (repetitive) outputs. At least 99.8% of outputs are in the requested language.
How we got here (FLORES+ devtest, mean chrF++, beam 4)
| Model | Mean chrF++ |
|---|---|
| Mateza Nova Pocket (250M) | 14.41 |
| OPUS-FT v3 (two models, 75M each) | 41.64 |
| Marian student, before this fine-tune | 42.95 |
| This model | 46.52 |
Domain test sets (500 held-out sentences each, greedy chrF++)
These are the test splits of the datasets used for the fine-tune. No test sentence is in the training data, but the sentences come from the same sources, so these numbers are in-domain. FLORES+ is the fairer general measure.
| Test set | en→rw before → after | rw→en before → after |
|---|---|---|
| Rwandan Gazette (legal) | 35.51 → 58.53 | 33.34 → 56.22 |
| Health | 46.61 → 66.18 | 39.34 → 63.15 |
| Education | 60.27 → 63.36 | 49.11 → 52.42 |
| TICO-19 test (COVID-19) | 38.83 → 42.81 | 36.55 → 41.68 |
Sample translations (greedy)
| Input | Output |
|---|---|
| Good morning, how are you? | Mwaramutse, amakuru? |
| Good evening, see you tomorrow. | Mwiriwe, ejo muzarebe. |
| Students must register before the end of the month. | Abanyeshuri bagomba kwiyandikisha mbere y'uko ukwezi kurangira. |
| Wash your hands with soap and clean water before eating. | Karaba intoki ukoresheje isabune n’amazi meza mbere yo kurya. |
| Karaba intoki n'isabune n'amazi meza mbere yo kurya. | Wash your hands with soap and water before eating. |
| Abana bagomba kujya ku ishuri buri munsi. | Children should go to school every day. |
| Mwaramutse, amakuru yanyu? | Good morning, are you? ❌ ("how are you?" expected) |
| The meeting starts at two in the afternoon. | Inama itangira saa sita. ❌ (should be "saa munani") |
The full reports are in eval/:
REPORT.mdandsummary.json: FLORES+ results;DOMAINS.mdanddomains.json: before and after, per domain;probes.tsv: the sample phrases;train-report.jsonanddata-report.json: the training run and the data counts.
Limitations
- Clock times are unreliable. Rwandan hours count from 6 a.m. ("saa munani" = 2 p.m.). Check any time, date or number.
- Some greetings are still weak in rw→en. For example, "Muramuke" is translated as "Next".
- No native-speaker review yet. The scores are automatic metrics. Don't use the output unreviewed for medical, legal or other high-stakes text.
- Input length: up to 256 tokens per call. Translate long documents sentence by sentence.
Training
| Architecture | Marian Transformer-base: 6 + 6 layers, width 512, 8 heads, FFN 2048, shared and tied embeddings, 61M parameters |
| Tokenizer | Our own 32k byte-level BPE, trained on English and Kinyarwanda, with language tags |
| Stage 1: student | Trained from scratch on the OPUS-FT v3 data mix (about 161k pairs) and on translations of the same sentences by the v3 models (sequence-level distillation), in both directions: 623,395 rows |
| Stage 2: this fine-tune | Continued from stage 1 on 296,024 new screened pairs, in both directions, together with all 623,395 stage-1 rows so the model keeps what it learned: 1,246,131 rows |
| Fine-tune settings | LR 1.5e-4, 3% warmup, inverse-square-root decay, batch 128, fp16, dropout 0.1, max length 128, early stopping after 2 epochs without improvement (best checkpoint at epoch 8) |
| Model selection | Mean of FLORES+ dev chrF++ and a 600-row domain dev set. FLORES+ devtest and the domain test sets were used only for the final scores. |
| Hardware | Kaggle, 2× Tesla T4, about 7 hours for the fine-tune |
New data in stage 2
Every row went through the same checks:
- broken characters fixed or removed;
- untranslated rows dropped;
- length and length-ratio checks;
- markup and scripture filters;
- LaBSE alignment, at 0.70 for web-mined sources and 0.50 for human-translated ones (not applied to the curated packs);
- duplicates removed, including against the stage-1 data;
- anything that appears in FLORES+ or in a held-out test or dev split removed.
| Source | Domain | Pairs |
|---|---|---|
| Digital Umuganda Rwandan Gazette | legal / government | 149,314 |
| Digital Umuganda Health | health | 60,073 |
| OPUS Wikimedia | encyclopedic | 27,893 |
Kira-Floris expert_en_rw |
mixed | 20,477 |
| mbazaNLP Education (full public set) | education | 19,124 |
| Digital Umuganda MT dataset (rows not in v3) | general / conversational | 11,084 |
| google/smol (SmolSent, SmolDoc) | everyday | 4,731 |
| Mateza Ice Block curated packs | everyday phrases | 1,618 (seen ×4) |
| TICO-19 dev | COVID-19 | 926 |
| OPUS translatewiki, Tatoeba; interuro | interface, everyday | 784 |
Left out on purpose:
- machine-translated sets;
- software interface strings (GNOME, KDE, Ubuntu);
- more web-mined NLLB;
- a scripture set with its stopwords stripped.
Data and attribution
The model weights are released under Apache-2.0. Training data, with the licences as stated by each source:
- Digital Umuganda:
- Rwandan Gazette: CC-BY-4.0.
- Health: no licence stated.
- Kinyarwanda–English MT dataset: CC-BY-4.0.
- mbazaNLP: Education, CC-BY-2.0.
- Google: SMOL, CC-BY-4.0.
- TICO-19: gmnlp/tico19, CC0.
- OPUS:
- Wikimedia: CC-BY-SA.
- translatewiki: CC-BY.
- Tatoeba: CC-BY 2.0 FR.
- J. Tiedemann, Parallel Data, Tools and Interfaces in OPUS (LREC 2012).
- Kira-Floris: cross-lingual-translation-en-kin-parquet,
expert_en_rwconfig; no licence stated. - Hamed-PRO: interuro-kinyarwanda-sentences-english, MIT.
- NLLB: mined bitext (allenai/nllb), ODC-BY 1.0, via the v3 data mix.
- Mateza: Ice Block curated packs.
- Teachers: mateza/mateza-opus-ft-v3, which was fine-tuned from Helsinki-NLP OPUS-MT (Tiedemann and Thottingal, EAMT 2020).
Evaluation uses FLORES+ (CC-BY-SA-4.0), which isn't redistributed here.
Files
| Path | Content |
|---|---|
model.safetensors, config.json, generation_config.json |
The model, fp32 |
tokenizer.json, tokenizer_config.json |
The 32k BPE tokenizer, with the language tags |
translator.py |
Translator: translate(text, direction) for both directions, with length-sorted batching, greedy or beam search, and fp16 on GPU |
eval/ |
FLORES+ and domain reports, sample phrases, training and data reports |
requirements.txt |
Dependencies |
Citation
@misc{mateza-marian-en-rw,
title = {Mateza Marian en-rw: one-model English-Kinyarwanda translation},
author = {Rukundo, Promesse and Kayondo, Mateza},
year = {2026},
note = {Distilled from Mateza OPUS-FT v3, then fine-tuned on Gazette, Health, Education and everyday data. Organization: Mateza.},
url = {https://huggingface.co/mateza/mateza-marian-en-rw}
}