blackbar-nano
Find and redact PII locally. 35 entity types, no GPU needed, about 38 ms per message on a base M1. No remote inference API calls, text never leaves the machine. Apache-2.0.
pip install blackbar-nano
from blackbar_nano import Redactor
redactor = Redactor() # downloads weights from Hugging Face on first run
print(redactor.redact("Call Sarah at 503-555-0142 or sarah.m@gmail.com"))
# Call [FIRST_NAME] at [PHONE_NUMBER] or [EMAIL]
for span in redactor.detect("My SSN is 523-04-1178"):
print(span.label, span.start, span.end, f"{span.score:.2f}")
# national_id_number 10 21 1.00
Optional NVIDIA GPU use
CPU is the tested default. With a CUDA-enabled PyTorch installation, the model can also run on an NVIDIA GPU:
redactor = Redactor(device="cuda")
The published latency numbers below are CPU measurements. CUDA performance has not been benchmarked for this release.
Why this exists
Most PII redaction either ships your text to an API, which defeats the point, or runs a model too heavy to use interactively. blackbar-nano is built for the local-first case. Redact before text leaves the trust boundary, like scrubbing a prompt before it goes to a cloud LLM. It's fast enough to sit in the hot path, not just run as an overnight batch job.
How it works
You give it text. It looks for spans that might be PII, then labels each one as one of 35 types. Two thresholds control the tradeoff: one decides if a span is worth flagging at all, the other decides how sure the model needs to be about the label before it commits.
The default thresholds are tuned for redaction, not textbook accuracy. The model would rather flag something and be a little wrong than miss it. If you'd rather have fewer false alarms and don't mind missing more, you can raise the thresholds yourself, per call:
spans = redactor.detect(
text,
entity_threshold=0.90,
label_threshold=0.70,
)
Span.score is the product of the entity and label confidence, not
either raw score by itself.
Longer text gets split into overlapping chunks automatically, so long documents work too, not just short messages.
Labels
first_name last_name full_name person date_of_birth email
phone_number address street_address city state_or_region
postal_code country national_id_number passport_number
drivers_license_number license_number tax_id account_number
routing_number iban card_number card_cvv username ip_address
account_id password api_key sensitive_date medical_record_number
health_insurance_id medical_condition medication case_number url
Numbers
Both models were evaluated on byte-identical text. Full methodology and a 400-row verification slice are in this repo.
These are task-level comparisons of the released configurations, not architecture-matched experiments. blackbar-nano used its shipped 0.60 entity and 0.50 label thresholds; GLiNER2-PII used its documented 0.50 inference threshold and was not separately threshold-tuned for these comparisons. Scores are restricted to mutually mapped PII concepts where applicable.
| blackbar-nano | GLiNER2-PII | |
|---|---|---|
| Redaction coverage (3,895-row eval) | 96.9% | 95.8% |
| False-alarm share | 10.9% | 22.4% |
| SPY public benchmark (avg exact F1) | 0.518 | 0.390 |
| Latency per message (M1 CPU, 400-row comparison) | ~36 ms | ~2.3 s |
Full evaluation details live on the model card. How it was built, including the two training runs that failed first, is in the technical write-up.
Limitations
Being upfront about what this model isn't good at yet.
- URLs are weak. It misses about two out of three URLs. If you need to catch URLs reliably, pair this with a simple regex as a workaround.
- English only. Not trained or tested on other languages.
- Multi-word spans can split. "John Smith" might come back as two separate spans instead of one. For redaction this still works fine, both pieces get covered. If you need one clean span per entity, this will cost you some accuracy.
- Fixed labels only. No custom entity types, the head is a closed 35-way classifier.
- Mixed training distribution. Training combined public annotated corpora with synthetic conversational examples. This provides broader register coverage than synthetic data alone, but unusual formats and deployment-specific language may still be underrepresented.
Verify the claims yourself
python eval/run_eval.py # coverage, on the included 400-row slice
python eval/run_spy_eval.py # SPY score, on a frozen 300-doc SPY instance
The SPY script should reproduce an average exact F1 close to 0.518. Small differences across hardware and library versions are possible. The coverage script should produce about 97.5% on the included 400-row slice. That slice is separate from the 3,895-row evaluation behind the 96.9% headline, so the two coverage figures are expected to differ slightly. Stray share comes out higher here though, around 15%, not the 10.9% figure in the table earlier, and that's worth explaining rather than glossing over. The two numbers come from different annotation scopes. This 400-row public slice only has gold labels for nano's 35 shipped types, so a prediction landing on some other real entity outside that set counts as a false alarm here, even though the model found something genuine. The 10.9% figure was measured against a broader annotation scope that isn't part of this public release.
For the full head-to-head against GLiNER2-PII, here's what I measured separately, same 400 rows, same machine, using comparison tooling that isn't part of this repo. GLiNER2-PII isn't mine to redistribute:
| blackbar-nano | GLiNER2-PII | |
|---|---|---|
| Coverage | 97.43% (14 missed of 545 mapped comparison spans) | 96.70% (18 missed) |
| Stray share | 10.9% | 22.3% |
| Latency per message (M1 CPU, 400-row comparison) | ~36 ms | ~2.3 s |
That 545 is a different, narrower count than the 594 annotations in the
public eval_slice_400.jsonl. This comparison used a different label
filter than the public verification slice does.
Numbers on this smaller slice run a little higher than the full 3,895-row eval above, since it's a cleaner sample, but the gap between the two models holds either way.
I've also run blackbar-nano by itself on a Windows laptop, an Intel Xeon E-2276M, 12 cores, 32GB RAM. Coverage came out 97.43%, stray 10.85%, both almost identical to the Mac numbers above, so accuracy held steady on the two machines I've tested so far. Latency was about 271 ms per message though, versus about 38 ms on the M1. The Windows machine is an older mobile chip, not a modern desktop or server one, which likely explains most of that gap, though two machines isn't enough to fully separate hardware age from other differences between them.
The GLiNER2-PII head-to-head above is still Mac-only. That one's too slow to run on this Windows machine. If you're on Windows or Linux and want to run that comparison yourself, or just want to add another data point on different hardware, I'd like to see it. Open an issue with your numbers.
Acknowledgements
Built on ModernBERT-base from Answer.AI.
Benchmarked against GLiNER2-PII from Fastino, the model this one is trying to beat.
Evaluated on SPY from mks-logic (CC-BY-4.0), a public PII benchmark built from legal and medical text. The SPY eval file in this repo is my own generated instance, built from their placeholder templates with a fixed random seed, not their original files.
License
Weights and inference code: Apache-2.0. eval/spy_eval_300.jsonl is
derived from mks-logic/SPY and stays under its CC-BY-4.0 license.
Everything else original in this repo is Apache-2.0. Training data and
the data-preparation pipelines, including public-dataset conversion and
synthetic generation, are not released. Training sources and their
individual licenses: TRAINING_DATA.md. Not
affiliated with bnosac/blackbar (R) or the blackbar desktop utility
(PyPI).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file blackbar_nano-0.1.0.tar.gz.
File metadata
- Download URL: blackbar_nano-0.1.0.tar.gz
- Upload date:
- Size: 16.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0b72c08a44e911ce6b78e70574c1c67dfb9bbf0f0da6f8d94597b31e9fd3f606
|
|
| MD5 |
d7aa56ae57ebb1e97768b2764e72239e
|
|
| BLAKE2b-256 |
9456525257a3b2b646243e8dfcc8b82a814efdd10a7e2bd1a20ae7583fe8f182
|
File details
Details for the file blackbar_nano-0.1.0-py3-none-any.whl.
File metadata
- Download URL: blackbar_nano-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8709ac34b56f74a10ce43c5c88c8c24b31da33e00c78b4260eeb23ebce2847e
|
|
| MD5 |
9f688ee2e8668cdd1ef035030817a14f
|
|
| BLAKE2b-256 |
a47f60ab82a0e872fe02993410c2ce61d7f2d22dfc9196a987351becc60495cd
|