Token Classification
Transformers
ONNX
openai_privacy_filter
pii
multilingual
openai-privacy-filter
ner
Instructions to use lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx")# Load model directly from transformers import AutoTokenizer, AutoModelForTokenClassification tokenizer = AutoTokenizer.from_pretrained("lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx") model = AutoModelForTokenClassification.from_pretrained("lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 8,316 Bytes
7905a7e 325259e 7905a7e 325259e 7905a7e 325259e 7905a7e 325259e 7905a7e 325259e 7905a7e 325259e 7905a7e 325259e 7905a7e 325259e 7905a7e 325259e ef51f9a 325259e 7905a7e 325259e ef51f9a 325259e 7905a7e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | ---
license: apache-2.0
datasets:
- ai4privacy/pii-masking-200k
- ai4privacy/pii-masking-400k
- ai4privacy/pii-masking-openpii-1m
- piimb/privy
- gretelai/gretel-pii-masking-en-v1
- nvidia/Nemotron-PII
language:
- ar
- bn
- de
- en
- es
- fr
- hi
- it
- ja
- ko
- nl
- pt
- te
- tr
- vi
- zh
base_model:
- openai/privacy-filter
- OpenMed/privacy-filter-multilingual-v2
pipeline_tag: token-classification
library_name: transformers
tags:
- token-classification
- pii
- multilingual
- openai-privacy-filter
- ner
---
# OpenMed Privacy Filter Multilingual v2 - ONNX
This is an [ONNX](https://onnx.ai/) export of [`OpenMed/privacy-filter-multilingual-v2`](https://huggingface.co/OpenMed/privacy-filter-multilingual-v2), converted for CPU inference with [`onnxruntime`](https://onnxruntime.ai/)'s `CPUExecutionProvider`. No fine-tuning was performed - this repo only changes the runtime format, not the model's weights or behavior.
## About the base model
All credit for the model itself goes to **[OpenMed](https://huggingface.co/OpenMed)**. From their model card: `privacy-filter-multilingual-v2` performs fine-grained PII extraction across **54 entity categories in 16 languages**, built on top of **OpenAI's Privacy Filter** (a Mixture-of-Experts transformer, 128 experts with top-4 routing per token) with a BIOES token-classification head, and extended with multilingual coverage and expanded entity types by OpenMed and the AI4Privacy community. See the [original model card](https://huggingface.co/OpenMed/privacy-filter-multilingual-v2) for the full entity list, training details, and citation.
This conversion exists only because no ONNX export was published alongside the original safetensors weights.
## What was done for this conversion
The checkpoint's `config.json` declares a custom `model_type`/architecture name (`openai_privacy_filter` / `OpenAIPrivacyFilterForTokenClassification`). Depending on your installed `transformers` version this may already be a recognized, first-class architecture (loadable directly via `AutoModelForTokenClassification`/`AutoTokenizer`) - that's what this export uses. If you're on an older `transformers` release without native support, do **not** substitute `transformers`' generic `gpt_oss` classes as a stand-in: despite matching tensor shapes (the checkpoint loads with zero missing/unexpected weight keys either way), the two architectures differ in a way that silently produces near-total false negatives - this model is **bidirectional** with sliding-window attention, while `gpt_oss` is causal/decoder-style. That mismatch doesn't error out; it just quietly predicts "O" (no entity) almost everywhere, while looking numerically self-consistent. If you hit that, upgrade `transformers` instead of working around it.
Two things don't just fall out of a plain `torch.onnx.export` call, either way:
1. **MoE routing.** The expert-routing module has two forward code paths: a data-dependent, per-batch Python loop (`.nonzero()`-driven "which experts got hit") used at runtime, which isn't traceable for export; and a fully vectorized dense computation over all 128 experts (weighted by mostly-zero routing weights), which is traceable and mathematically identical. This export monkeypatches the routing module to always use the dense path. Getting this exactly right matters: the routing weights arrive as a **sparse** `(num_tokens, top_k)` pair (`router_indices`, `routing_weights`), not a dense `(num_tokens, num_experts)` tensor, so they have to be scattered into a dense per-expert weight matrix first; and this checkpoint's gate/up projection uses a **concatenated** split (`gate, up = gate_up.chunk(2, dim=-1)`), not the interleaved split (`gate_up[..., ::2]`/`[..., 1::2]`) some sibling MoE architectures use. Either bug produces an export that's internally self-consistent (matches its own incorrect PyTorch reference to ~1e-5) but wrong relative to the true model - only comparing against a real forward pass on a known example (e.g. does it actually tag "John Smith" as a name?) catches it.
2. **Precision.** The checkpoint's `config.json` declares a default of bf16. Loading in that precision and exporting produces `Where` nodes with bf16 scalar operands that `onnxruntime`'s `CPUExecutionProvider` doesn't have a kernel for (a `NOT_IMPLEMENTED` error at session-load time, not export time). This export explicitly loads and exports in full **fp32**.
## Verification
The exported graph was validated against a native, unpatched PyTorch forward pass of the original checkpoint on the same inputs (both loaded fp32), running on `onnxruntime`'s `CPUExecutionProvider`:
- **Max absolute logit difference (ONNX vs. true native PyTorch): ~1.8e-5**
- Spot-checked against a plain-English sentence containing a name, phone number, and email - all three correctly tagged with their entity types and BIOES boundaries.
## Usage
```python
import numpy as np
import onnxruntime as ort
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx")
session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
text = "My name is John Smith, call me at 555-123-4567 or email john@example.com."
encoded = tokenizer(text, return_tensors="np")
(logits,) = session.run(None, {
"input_ids": encoded["input_ids"],
"attention_mask": encoded["attention_mask"],
}) # logits: [batch, seq_len, 217] (BIOES over 54 entity types + "O")
```
No custom mask construction needed - the graph takes a plain 2D `input_ids` + `attention_mask`, same convention as a standard BERT-family token classifier.
## Using this with the Lemonade router
This repo is self-contained for [Lemonade](https://github.com/lemonade-sdk/lemonade)'s `onnxruntime` backend (via [`ort-server`](https://github.com/lemonade-sdk/ort-server)) - `model.onnx` + `model.onnx.data` + `tokenizer.json` + `config.json` + `manifest.json` all sit together at the repo root, so it can be registered directly as a checkpoint with no extra file staging.
**1. Register the classifier:**
```bash
curl -X POST http://localhost:13305/v1/pull -H "Content-Type: application/json" -d '{
"model_name": "user.privacy-filter-ml-v2-onnx",
"checkpoint": "lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx",
"recipe": "onnxruntime"
}'
```
**2. Register the router policy** - a ready-to-use `collection.router` policy that routes to a local model whenever any of this classifier's 216 non-"O" labels crosses `min_score: 0.5`, and to a cloud model otherwise, is published separately at [`lemonade-sdk/pii_policy_openmed-privacy-filter-multilingual-v2-onnx`](https://huggingface.co/lemonade-sdk/pii_policy_openmed-privacy-filter-multilingual-v2-onnx):
```bash
hf download lemonade-sdk/pii_policy_openmed-privacy-filter-multilingual-v2-onnx --local-dir .
curl -X POST http://localhost:13305/v1/pull -H "Content-Type: application/json" \
--data-binary @pii_policy_openmed-privacy-filter-multilingual-v2-onnx.json
```
The policy's `routing.candidates` (`Qwen3.5-0.8B-GGUF` local / `fireworks.kimi-k2p6` cloud) and `min_score` are starting points, not fixed requirements - swap either to whatever local/cloud models you have configured. On the Nemotron-PII benchmark (20,000 PII-bearing prompts), this policy scored a 0% leak rate (zero PII prompts routed to the cloud candidate).
For building your own routing policy from scratch, or a more sophisticated one (multiple classifiers, LLM-based routing, custom match logic) rather than adapting this one, see the [`lemonade-router-builder`](https://github.com/amd/skills/tree/main/skills/lemonade-router-builder) skill - it turns a natural-language description of routing intent into a valid `collection.router` policy JSON.
## License
Apache 2.0, inherited from the base model.
## Citation
Please cite the original model and its underlying work:
```bibtex
@misc{openmed-privacy-filter-v2,
title = {OpenMed Privacy Filter Multilingual v2},
author = {OpenMed},
year = {2026},
url = {https://huggingface.co/OpenMed/privacy-filter-multilingual-v2}
}
```
See the [original model card](https://huggingface.co/OpenMed/privacy-filter-multilingual-v2) for the complete BibTeX entry crediting OpenMed, OpenAI, and the AI4Privacy contributors.
|