Views
No views yet
OpenMed/privacy-filter-nemotron
for fast, on-device PII detection on Apple Silicon. This BF16 artifact
preserves the full source precision; for a smaller / faster sibling, see
OpenMed/privacy-filter-nemotron-mlx-8bit.Family at a glance. Same architecture and training data, three runtimes:
- PyTorch —
OpenMed/privacy-filter-nemotron— CPU + CUDA.- MLX BF16 (this repo) — Apple Silicon, full precision (~2.6 GB).
- MLX 8-bit —
OpenMed/privacy-filter-nemotron-mlx-8bit— Apple Silicon, ~1.4 GB, ~1.7× faster.
openai_privacy_filter model type used by
openai/privacy-filter).
It tags each token with a BIOES label across 55 PII span classes, then
a Viterbi pass over the BIOES grammar yields clean entity spans. Detected
categories include:first_name, last_name, user_name, gender, age, date_of_birthemail, phone_number, fax_number, street_address, city, state, country, county, postcode, coordinatessn, national_id, tax_id, certificate_license_numberaccount_number, bank_routing_number, credit_debit_card, cvv, pin, swift_bicmedical_record_number, health_plan_beneficiary_number, blood_typecompany_name, occupation, employee_id, customer_id, employment_status, education_levelurl, ipv4, ipv6, mac_address, http_cookie, api_key, password, device_identifierrace_ethnicity, religious_belief, political_view, sexuality, languagelicense_plate, vehicle_identifierdate, date_time, timebiometric_identifier, unique_idO plus B-, I-, E-, S- for each of the 55
span classes (4 × 55 + 1 = 221). The runtime PrivacyFilterMLXPipeline
runs Viterbi over this BIOES grammar, so the consumer sees clean grouped
entities rather than raw token tags.id2label.json is shipped alongside the weights in this repo.| Field | Value |
|---|---|
| Source model type | openai_privacy_filter |
| Source architecture | OpenAIPrivacyFilterForTokenClassification |
| Hidden size | 640 |
| Transformer layers | 8 |
| Attention | Grouped-Query (14 query heads / 2 KV heads, head_dim=64) with attention sinks |
| FFN | Sparse Mixture-of-Experts — 128 experts, top-4 routing, SwiGLU |
| Position encoding | YARN-scaled RoPE (rope_theta=150_000, factor=32) |
| Context length | 131,072 tokens (initial 4,096) |
| Tokenizer | o200k_base (tiktoken) — vocab 200,064 |
| Output head | Linear(640 → 221) with bias |
| File | Size | Purpose |
|---|---|---|
weights.safetensors | 2.6 GB | BF16 model weights in OpenMed-MLX layout |
config.json | 19 KB | Model + MLX runtime config |
id2label.json | 5.4 KB | Numeric ID → BIOES label string |
openmed-mlx.json | 0.7 KB | OpenMed MLX manifest (task, family, runtime hints) |
tokenizer.json, tokenizer_config.json | 27 MB | Source tokenizer files (kept for reference) |
tiktoken o200k_base directly for tokenization;
the tokenizer.json is kept so consumers can inspect or re-tokenize via
transformers if desired.extract_pii() / deidentify() API that
auto-selects MLX on Apple Silicon and PyTorch elsewhere — same code on
every host.pip install -U "openmed[mlx]"1from openmed import extract_pii, deidentify
2
3text = (
4 "Patient Sarah Johnson (DOB 03/15/1985), MRN 4872910, "
5 "phone 415-555-0123, email sarah.johnson@example.com."
6)
7
8# Extract grouped entity spans (runs on MLX here, PyTorch fallback elsewhere)
9result = extract_pii(text, model_name="OpenMed/privacy-filter-nemotron-mlx")
10for ent in result.entities:
11 print(f"{ent.label:30s} {ent.text!r} conf={ent.confidence:.2f}")
12
13# De-identify
14masked = deidentify(text, method="mask",
15 model_name="OpenMed/privacy-filter-nemotron-mlx")
16fake = deidentify(
17 text,
18 method="replace",
19 model_name="OpenMed/privacy-filter-nemotron-mlx",
20 consistent=True,
21 seed=42, # deterministic locale-aware Faker surrogates
22)mlx package),
this exact same call automatically falls back to the PyTorch checkpoint
OpenMed/privacy-filter-nemotron
with a one-time warning. Family-aware fallback: a Nemotron MLX request never
substitutes the unrelated openai/privacy-filter baseline.1from huggingface_hub import snapshot_download
2from openmed.mlx.inference import PrivacyFilterMLXPipeline
3
4model_path = snapshot_download("OpenMed/privacy-filter-nemotron-mlx")
5pipe = PrivacyFilterMLXPipeline(model_path)
6
7print(pipe("Email me at alice.smith@example.com after 5pm."))
8# [{'entity_group': 'email',
9# 'score': 0.92,
10# 'word': 'alice.smith@example.com',
11# 'start': 12,
12# 'end': 35}]entity_group, score, word,
start, and end (character offsets into the input string).1from openmed.mlx.models import load_model
2import mlx.core as mx
3
4model = load_model("/path/to/privacy-filter-nemotron-mlx")
5ids = mx.array([[1, 100, 200, 300]], dtype=mx.int32)
6mask = mx.ones((1, 4), dtype=mx.bool_)
7logits = model(ids, attention_mask=mask) # shape (1, 4, 221)mlx>=0.18. The MLX runtime in this repo is
independent of mlx_lm (token classification, not causal LM).-mlx-8bit
sibling instead.opf training/eval CLI). The MLX port
in this repo runs that same architecture under Apple's MLX framework.