Instructions to use mad-nasher/rabwa-mask with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use mad-nasher/rabwa-mask with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="mad-nasher/rabwa-mask")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("mad-nasher/rabwa-mask", device_map="auto") - Notebooks
- Google Colab
- Kaggle
rabwa-mask
rabwa-mask is a small, CPU-only model that finds personal data in Arabic (Gulf / Saudi dialect) and English text.
It ships with a reversible redaction helper. The typical flow:
- Mask: names, phone numbers, IDs, IBANs and home addresses become placeholders such as
[PERSON_1]. - Send the masked text to any external LLM API.
- Restore: the real values go back into the reply on your side.
The raw text never leaves your server.
in : Please call Sara Al-Harbi on 0551234567 or write to sara.h@example.com
out : Please call [PERSON_1] on [PHONE_1] or write to [EMAIL_1]
reply: Sure, we will call [PERSON_1] today. -> Sure, we will call Sara Al-Harbi today.
The same works for Arabic script, mixed Arabic/English text and Arabic-Indic digits.
Why this model
Teams that pass customer notes, chat messages or support tickets to a hosted LLM leak personal data in free text. Regexes catch phone numbers and e-mails, but not names.
Arabic names are especially hard to catch:
- Structure: they have 2 to 4 parts, with bin / bint, the article al-, and kunyas such as Abu Fahd / Umm Khalid.
- Glued prepositions: one-letter prepositions and conjunctions attach directly to the name.
- Names that are also words: many names are ordinary words too, like Jameel, Kareem, Amal and Noor.
- Places named after people: streets, banks and hospitals carry people's names.
General multilingual NER models and small chat LLMs miss too many of these. rabwa-mask was fine-tuned for exactly this job, and it runs in a few milliseconds on a CPU.
Use cases
- Safe LLM calls. Mask CRM notes, tickets or chat messages before sending them to a hosted LLM, then restore the names in the answer.
- Customer-service and sales assistants. Let an internal assistant answer questions about customers while the model provider only ever sees codes.
- Log and analytics scrubbing. Remove personal data from application logs, call notes or survey answers before storing or sharing them.
- Dataset anonymisation. Clean Arabic / English text corpora before using them for training, evaluation or research.
- Document review. Find and highlight personal data in contracts, forms or e-mails before they are shared outside the team.
- Compliance. Support data-minimisation duties under privacy laws such as Saudi PDPL or GDPR. It is one technical control, not a legal guarantee.
- Privacy "try it" pages. Show staff exactly what an external model would receive. The helper returns the masked text, the detected values and the restored text.
What it detects
| Label | What | Found by |
|---|---|---|
PERSON |
full or partial names, kunyas, English spellings | model |
ADDRESS |
a person's home address (district, street, building), Saudi national short address | model + rule |
PHONE |
Saudi mobiles and landlines in any format: 05..., +966..., 00966..., spaces, dashes, Arabic-Indic digits | rules |
EMAIL |
e-mail addresses | rules |
NID |
Saudi national ID (10 digits, starts with 1) | rules |
IQAMA |
Saudi residence permit (10 digits, starts with 2) | rules |
IBAN |
Saudi IBAN (SA + 22 digits, spaced or not) | rules |
PLATE |
Saudi car plates, Arabic or Latin letters | rules |
- Who owns which label: the model also tags the fixed formats. Regexes are exact there, so the helper lets the rules own them and takes only names and addresses from the model.
- Tag scheme: BIO tags:
O, thenB-andI-for each of the eight labels.
Quick start (Python, ONNX, CPU)
git clone https://huggingface.co/mad-nasher/rabwa-mask && cd rabwa-mask
pip install -r requirements.txt # onnxruntime, tokenizers, numpy
python pii_redact.py "Please call Sara Al-Harbi on +966 55 123 4567, her IBAN is SA44 2000 0001 2345 6789 1234"
from pii_redact import Redactor
r = Redactor(threads=1) # load once, reuse
masked, mapping = r.redact(user_text) # send `masked` to the LLM
answer = r.restore(llm_reply, mapping) # real values back; unknown placeholders raise
- One mapping per request. Pass the same
mappingtoredact()for every field of one request. Keep it in memory only and drop it afterwards. - Same value, same placeholder. A value that appears twice gets one placeholder, and every occurrence in the text is masked.
- Fail closed. If the model can't be loaded or raises, don't send the text.
Using it with transformers
The full-precision weights (model.safetensors) work with the standard token-classification pipeline. Use this route for further fine-tuning or for GPU batch jobs:
from transformers import pipeline
ner = pipeline(
"token-classification",
model="mad-nasher/rabwa-mask",
aggregation_strategy="simple",
)
for ent in ner(text):
print(ent["entity_group"], ent["start"], ent["end"], round(ent["score"], 3))
The pipeline returns the model's raw spans. Masking, placeholders and the regex rules live in pii_redact.py; reuse its RULES if you need the exact phone / ID / IBAN behaviour.
Using it from Node.js
The ONNX file runs with onnxruntime-node and a plain BERT WordPiece tokenizer (tokenizer.json, no lower-casing).
- Model inputs:
input_ids,attention_mask,token_type_ids. - Model output: one logit per label for each token; take the arg-max.
- Windows: split long text into windows of about 300 characters (128 tokens).
Results
These numbers come from a held-out synthetic test set of 780 Gulf-Arabic / English sentences:
- What the sentences are: CRM-style notes, assistant questions and WhatsApp-style messages, built from random combinations of common names.
- Sentences with no personal data: 179.
- Hard negatives: 545 company, bank, place, street and project names, plus words that are also names.
- Unseen material: the test sentence templates, and a quarter of the names, were never seen in training.
"Fully masked" means every letter of the entity is covered. Masking half a name counts as a leak.
| Fully masked | Precision | Recall | |
|---|---|---|---|
names (PERSON, 537) |
97.2% | 0.93 | 0.98 |
addresses (ADDRESS, 110) |
99.1% | 0.74 | 0.99 |
| phone / e-mail / ID / iqama / IBAN / plate (326) | 100% | 1.00 | 1.00 |
| all personal data | 98.4% | ||
| hard negatives wrongly masked | 10 of 545 (1.8%) |
- Adding a name list helps. Combined with a list of common first and family names, and a short list of your own brand and product names that must never be masked, name masking reached 99.4% in our tests (99.6% for all personal data).
- The data is synthetic. Real text will be messier. Test the model on your own data before relying on it.
Speed and size
Measured on Redactor.detect, Apple M3 CPU; a server CPU will differ.
| 1 thread | 4 threads | |
|---|---|---|
| short message (~55 chars) | ~12 ms | ~5 ms |
| 1,500 characters | ~330 ms | ~105 ms |
- ONNX model: 105 MB (int8, per-channel; same accuracy as fp32).
- Memory: a process running it needs about 250 MB of RAM.
Compared with alternatives
Same test set. Each alternative was combined with the same regex rules and name list.
| Model | Names fully masked | Short-request time (1 thread) | Size |
|---|---|---|---|
| rabwa-mask | 99.4% | ~12 ms | 105 MB |
| CAMeLBERT-NER, off the shelf | 82% | similar | 414 MB |
| GLiNER multi v2.1, zero-shot | 92% | ~36 ms | 1.1 GB |
| Qwen2.5-3B-Instruct, prompted | 87% | ~4 s | 1.9 GB |
Known limits
- One-word names that are also common words can be missed when they appear alone, for example a greeting that ends with the name Amal.
- Some places named after people get masked too, such as streets named after princes or a hospital named after a doctor. Over-masking is the safe direction.
- Only Saudi formats are covered by the phone, ID, IBAN and plate rules. Add your own rules for other countries.
- Text is read in windows of about 300 characters (128 tokens), so long documents take longer.
- Not a legal guarantee. Use it as one layer of a privacy design. Keep logging off for raw text, keep mappings in memory only, and fail closed.
Files
| File | |
|---|---|
onnx/model.int8pc.onnx |
int8 ONNX model used by pii_redact.py (CPU) |
model.safetensors |
full-precision PyTorch weights (transformers, fine-tuning) |
config.json |
model config and label map |
tokenizer.json, tokenizer_config.json, special_tokens_map.json, vocab.txt |
BERT WordPiece tokenizer, no lower-casing |
pii_redact.py |
detection + reversible redaction helper |
requirements.txt |
Python dependencies for the helper |
SHA256SUMS |
checksums of the model files |
LICENSE, NOTICE |
Apache 2.0 licence and attribution |
Training
- Base model: fine-tuned from CAMeL-Lab/bert-base-arabic-camelbert-mix-ner.
- Data: 1,600 synthetic sentences.
- Settings: 6 epochs, learning rate 5e-5, on Apple MPS (about one minute).
- Stability: three training seeds gave the same accuracy within 0.3 points.
- Export: ONNX, then quantised to int8 per channel.
No real personal data was used. Every name in the training and test data is a random combination of common given and family names.
License
Apache License 2.0, the same as the base model. See LICENSE and NOTICE.
Citation
@misc{rabwa_mask_2026,
title = {rabwa-mask: reversible PII redaction for Gulf Arabic and English text},
author = {Nasher},
year = {2026},
url = {https://huggingface.co/mad-nasher/rabwa-mask}
}
Please also cite the CAMeLBERT base model:
@inproceedings{inoue-etal-2021-interplay,
title = {The Interplay of Variant, Size, and Task Type in Arabic Pre-trained Language Models},
author = {Inoue, Go and Alhafni, Bashar and Baimukan, Nurpeiis and Bouamor, Houda and Habash, Nizar},
booktitle = {Proceedings of the Sixth Arabic Natural Language Processing Workshop},
year = {2021}
}
Model tree for mad-nasher/rabwa-mask
Base model
CAMeL-Lab/bert-base-arabic-camelbert-mix-ner