Views
No views yet
OpenMed/privacy-filter-multilingual-v2, converted for CPU inference with onnxruntime's CPUExecutionProvider. No fine-tuning was performed - this repo only changes the runtime format, not the model's weights or behavior.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 for the full entity list, training details, and citation.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.torch.onnx.export call, either way:.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.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.onnxruntime's CPUExecutionProvider:1import numpy as np
2import onnxruntime as ort
3from transformers import AutoTokenizer
4
5tokenizer = AutoTokenizer.from_pretrained("lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx")
6session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
7
8text = "My name is John Smith, call me at 555-123-4567 or email john@example.com."
9encoded = tokenizer(text, return_tensors="np")
10(logits,) = session.run(None, {
11 "input_ids": encoded["input_ids"],
12 "attention_mask": encoded["attention_mask"],
13}) # logits: [batch, seq_len, 217] (BIOES over 54 entity types + "O")input_ids + attention_mask, same convention as a standard BERT-family token classifier.onnxruntime backend (via 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.1curl -X POST http://localhost:13305/v1/pull -H "Content-Type: application/json" -d '{
2 "model_name": "user.privacy-filter-ml-v2-onnx",
3 "checkpoint": "lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx",
4 "recipe": "onnxruntime"
5}'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:1hf download lemonade-sdk/pii_policy_openmed-privacy-filter-multilingual-v2-onnx --local-dir .
2curl -X POST http://localhost:13305/v1/pull -H "Content-Type: application/json" \
3 --data-binary @pii_policy_openmed-privacy-filter-multilingual-v2-onnx.jsonrouting.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).lemonade-router-builder skill - it turns a natural-language description of routing intent into a valid collection.router policy JSON.1@misc{openmed-privacy-filter-v2,
2 title = {OpenMed Privacy Filter Multilingual v2},
3 author = {OpenMed},
4 year = {2026},
5 url = {https://huggingface.co/OpenMed/privacy-filter-multilingual-v2}
6}