Views
No views yet
OpenMed/privacy-filter-nemotron,
a fine-grained PII token-classification model — a fine-tune of
openai/privacy-filter on the
nvidia/Nemotron-PII dataset. It labels
every token with a BIOES tag over 55 PII categories (221 classes) in a single forward pass,
then decodes coherent spans with a constrained Viterbi procedure — so it can be served locally
with no Python as the encoder/NER tier of a PII redactor.openai/privacy-filter covers 8
coarse categories, this fine-tune trades multilingual breadth for category depth: 55
fine-grained English categories (first/last name, government IDs, financial, healthcare,
vehicle, digital, …).For broader language coverage (54 categories across 16 languages) instead of this model's English-only depth, see the multilingual fine-tuneprivacy-filter-multilingualGGUF.
openai-privacy-filter, that is not (yet) part of
upstream llama.cpp. It runs on:1# build (see the repo README for CUDA/Vulkan)
2cmake --preset release && cmake --build --preset release -j
3# run
4echo "Contact John Doe at jdoe@example.com" | \
5 build/release/pf-cli --classify privacy-filter-nemotron-f16.gguf 0.5pf_load / pf_classify → entity spans with UTF-8 byte offsets;
pf_tokenize / pf_logits) shaped for FFI — see the repo README.TokenClassify RPC and runs the constrained BIOES Viterbi decode,
returning entity spans. LocalAI drives it through the privacy-filter backend (which
wraps privacy-filter.cpp). The model is not a chat/completion model — it is a PII detector
that other models opt into via a pii.detectors list.llama.cpp, llama-cpp-python, Ollama, and
LM Studio will fail to load this file (unknown model architecture: 'openai-privacy-filter'). The arch can be added with carry-patches (TOKEN_CLS pooling, the
architecture + HF→GGUF converter, the bidirectional banded-attention graph, and an all-SWA
no-cache mask fix; TOKEN_CLS pooling tracks the still-open
PR #19725). Until that support lands
upstream, privacy-filter.cpp above is the patch-free alternative.Pooling note (llama.cpp path only): the model must be loaded with TOKEN_CLS pooling (the GGUF's default). If you drivellama-embeddingdirectly for testing, do not pass--pooling none— that overrides the default and yields raw hidden states instead of label logits. privacy-filter.cpp handles this automatically.
| File | Precision | Size | Notes |
|---|---|---|---|
privacy-filter-nemotron-f16.gguf | F16 | 2.82 GB | Reference artifact. 156 tensors; 221 classifier.output_labels; pooling_type = TOKEN_CLS. |
privacy-filter-nemotron-q8.gguf | Q8_0 (experts) | 1.64 GB | MoE expert weights → Q8_0, the rest F16. For RAM-constrained / edge use. |
sha256 (f16): 70dfe91ff220ff04594168a83e296dcc2054449cde77f98d0e782edbb6a31f5a
sha256 (q8): 2ec11c154e572a2686f4d77e861b7f74e6917e09638fe9bd27156d48bd99e21aq8 stores the bulk of the weights (the MoE
expert matrices) as 8-bit integers instead of 16-bit floats — via
scripts/requant_q8.py,
with attention, embeddings and the classifier head left at F16. That cuts the download by ~42%
(2.82 GB → 1.64 GB) and is usually a bit faster on CPU.d_model=640, 128 experts, top-4 routing; ~1.5B total /
~50M active per token), bidirectional banded attention (symmetric sliding window 128,
attention sinks retained), interleaved (GPT-J) RoPE with YaRN (θ=150000, factor 32), o200k
(o200k_base) tokenizer, and a 221-way token-classification head (score → cls.output). The
architecture is identical to the rest of the privacy-filter family — only the fine-tuned
weights and the larger (221-class) head differ.transformers reference at F16: across the parity
prompt set (short / PII-dense / multilingual / a 3k-token document) the F16 GGUF agrees with HF
on 99.94% of per-token argmaxes — 100% up to ~300 tokens, with the only two flips being
argmax ties at ~3k positions (the F16-rounding regime) — at full-logit cosine ≥ 0.9995
(mean 0.999997). A wrong expert transpose would crater that cosine, so the two load-bearing
conversion choices — the expert gate_up chunk(2) split and the n_swa = 2·sliding_window
window mapping — are confirmed by it. privacy-filter.cpp re-derives the YaRN truncate=false
frequencies at load time (fed to ggml_rope_ext as freq_factors) so the same GGUF is
interchangeable across runtimes.scripts/convert.py
— a self-contained HF→GGUF converter (no llama.cpp dependency). The same converter is re-run by
CI and gated against the HF reference logits for the sibling models, so the published artifact
stays in parity.O plus B-/I-/E-/S- for each of 55 categories (1 + 55×4 = 221), spanning identity,
contact, address, dates/time, government IDs, financial, healthcare, enterprise IDs, vehicle, and
digital entities:first_name, last_name, user_name, age, gender, race_ethnicity,
sexuality, religious_belief, political_view, education_level, occupation,
employment_status, language, blood_type, biometric_identifieremail, phone_number, fax_number, urlstreet_address, city, county, state, country, postcode, coordinatedate, date_of_birth, date_time, timessn, national_id, tax_idaccount_number, bank_routing_number, swift_bic, credit_debit_card,
cvv, pin, passwordmedical_record_number, health_plan_beneficiary_numbercompany_name, customer_id, employee_id, unique_id,
certificate_license_numberlicense_plate, vehicle_identifieripv4, ipv6, mac_address, device_identifier, api_key, http_cookieid2label table is embedded in the GGUF (classifier.output_labels). See the
source card
for the canonical grouping.occupation, language, gender, state, race_ethnicity,
political_view, education_level) sit lowest (F1 ≈ 0.65–0.89) vs the strictly-formatted
identifiers (≥ 0.95). The GGUF reproduces the HF logits at F16, so these numbers carry over.openai/privacy-filter and OpenMed/privacy-filter-nemotron.privacy-filter.cpp). The model
itself is by OpenMed, fine-tuned from OpenAI's privacy-filter on NVIDIA's
Nemotron-PII dataset — please cite all of them (BibTeX in the
source card).