The most constrained computer most people will ever use is the one you are reading this on. Not the phone. The tab.
A browser tab gets no installer, no graphics card it can count on, a few hundred megabytes of memory before things get ugly, and a user whose patience for a progress bar is measured in seconds. It's the worst place in modern computing to put a neural network. We put ours there on purpose.
Why the model has to be in the tab
Anonymization-as-a-service is a contradiction. To use it, you upload the confidential document to the company promising to protect it. You haven't reduced your exposure. You've moved it, and added a vendor.
So our detection runs in your browser: the PDF parsing, the character recognition on scans, and the model that works out that Margarida Rodrigues is a person and 1249-300 Lisboa is an address, in seven languages, without being told which one. Which means the model has to fit in a tab. The first version didn't.
The alphabet tax
Our detector starts life as a multilingual BERT fine-tuned to find names, organizations and places. Good model. It also carries a vocabulary of 119,547 tokens spanning 104 languages.
We offer seven: English, French, German, Spanish, Italian, Dutch, Portuguese. All Latin script.
At 768 dimensions that vocabulary table is about 92 MB of a 178.5 MB file — more than half the model, and roughly 97% of everything a first-time visitor downloads. Inside it: Devanagari, Cyrillic, Hangul, Thai, Armenian, Hebrew, Arabic, kana, thirteen thousand CJK ideographs. Downloaded by every user. Used by none.
Call it the alphabet tax. Every visitor pays to carry 104 languages in order to read one. And here is the part we are less proud of: we built the tool that fixes this, proved it was safe, then left it as an optional command nobody ran. A build step that is opt-in is a build step that happens nowhere.
Deleting rows, not compressing photos
The standard answer is distillation: train a small student model to imitate a large teacher. It works, and it trades accuracy for size — not a trade you get to make quietly in a tool whose whole job is to not miss anything. So we didn't.
What we did instead is closer to deleting unused rows from a spreadsheet. No encoder weight is touched — the twelve layers that do the actual understanding are copied verbatim. The vocabulary table is quantized per-tensor, so a surviving row keeps its exact value whether its neighbours are there or not. And the tokenizer matches the longest piece available, so dropping pieces unreachable in Latin text leaves Latin text splitting identically.
Result: 119,547 tokens down to 68,875. 178.5 MB down to 139.6 MB, about 100 MB over the wire. And "near-identical" is not the claim. We ran both models over 77 real documents in all seven languages: zero differences in how text was split, zero differences across 2,219 detected entities, and a maximum difference in model output of 0.0000000000. Not a smaller model that performs about as well. The same model with dead weight removed.
Getting there cost us one bad week. Token identifiers appear in three places in the tokenizer file; we updated two. Every test passed, text segmented identically, and the model quietly returned different answers on sixty documents. The rule we kept: compare the raw model output, never the entity list. Entity lists agree by luck.
Twice we tried to go further
Vocabulary is still a third of what remains, much of it Latin script for languages we do not offer. Another 30 to 40 megabytes looked available. It is not a trade-off. It is a cliff.
Keeping 58,000 of the 68,875 Latin tokens — 84% of them — saved 8 MB and lost 13% of all detections. Then the control that ended the argument: a keep-list of only the tokens our test documents actually use — 22,800 kept, 46,000 thrown away — produced byte-identical segmentation and 100% agreement. So a small vocabulary costs nothing. A missing token costs everything. There is no curve to tune along, and re-segmentation fails hardest on rare words — which is exactly what proper nouns are.
One number closed it. The Latin characters for languages we do not sell into — Turkish ğ ı, Vietnamese ư ạ ế, Romanian ă ț, Czech ř ů, Hungarian ő, Nordic ø — come to 4,200 pieces. 3.2 MB. That is the alphabet of every foreign name in a European contract: prime personal data, in the documents most likely to hold it, for three megabytes. If our detection degrades on your name because your name is Turkish, we have built the wrong product.
Second attempt: DistilBERT — six layers instead of twelve, about 70 MB over the wire against our 100. Then we scored it against 137 hand-checked entities. Detection fell from 77.2% to 73.5% — small enough to talk yourself into. But the average hides the shape: German fell 13 points, English 9, Spanish 22. And the two models agreed on only 76% of what they found. It did not detect fewer entities. It detected a different set of the same size. "Fewer" you can manage. "Different" you cannot.
Minuscule
The model we ship is called Minuscule. Carolingian minuscule was the writing system standardized across Charlemagne's empire in the eighth century — one clean Latin script, deliberately narrow, which is why a scribe in Aachen could read a manuscript copied in Tours. Many languages, one alphabet, nothing extra. Also, it is very small.
68,875 tokens. 139.6 MB on disk, roughly 100 MB transferred once and cached for good. Not one encoder weight altered from the model it derives from. It carries its own build record — upstream model, settings, vocabulary sizes, the checksum of its own weights — because a model that cannot tell you where it came from has no business handling the thing you are protecting. It is published on Hugging Face — weights, tokenizer and build record — so the numbers above are yours to check rather than ours to assert.
What a hundred megabytes buys
Right now, somewhere, a paralegal is pasting a client’s contract into a chat window. A nurse is uploading a discharge summary. A caseworker is dropping a family's file into a free translation site. They are not reckless people. They are behind. The approved option needs a ticket, a security review and two weeks, and the deadline is Thursday.
The only version of "protected" that ever wins is the one that is already open. No install, no administrator, no permission, no meeting. Which means a browser tab. Which means the model fits in a browser tab. Which is why grown adults spent a week arguing about 3.2 megabytes of Turkish diacritics.
Unglamorous work, invisible by design. But it ends with a person on a laptop in an office you will never see, doing their job on time, and their client's name never leaving the room. A hundred megabytes, once. After that, it is yours.
Common questions
Does removing vocabulary from a model reduce its accuracy?
Not when the removed vocabulary is unreachable in the languages you support. No encoder weight is modified and every surviving embedding row is copied bit-for-bit, so for Latin-script text the result is numerically identical to the original — we measured zero differences across 2,219 entities in 77 documents. Cutting into vocabulary the model can reach is a different thing entirely, and it degrades sharply.
Why not just use a smaller model instead?
We tried. A six-layer DistilBERT of the same family is about 70 MB on the wire against our 100, but it scored 73.5% against 77.2% on our curated set, and the average hid a 22-point drop in Spanish and 13 in German. It also agreed with the shipped model on only 76% of its findings — a different set of entities, not a smaller one. Our benchmark against Microsoft Presidio explains how we measure this.
Can a model this size really run in a browser tab?
Yes, with two caveats. The download happens once and is cached on your device afterwards, and the model still has to be compiled into a session at each launch, which is why there is a loading bar on the first run. Everything after that is local: no page of your document is uploaded. Why we built our own detection engine covers the rest of the pipeline.
Which languages does it cover, and what happens to the rest?
English, French, German, Spanish, Italian, Dutch and Portuguese, plus any other Latin-script language whose characters survive in the vocabulary — Turkish, Vietnamese, Romanian, Czech, Hungarian and Nordic characters were deliberately kept, because foreign names appear constantly in European contracts. A language in a dropped script, such as Japanese, needs its own model pack rather than one shared model that grows with every language added.