Instructions to use litert-community/LFM2.5-Encoder-350M-PII-Detector with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use litert-community/LFM2.5-Encoder-350M-PII-Detector with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
LFM2.5-Encoder-350M-PII-Detector β LiteRT
LiquidAI/LFM2.5-Encoder-350M-PII-Detector converted to LiteRT (.tflite) for on-device inference. Detects ~40 kinds of personal information across 16 languages, fully offline β a natural fit for on-device redaction where the text must never leave the phone (demo Space).
Model description
| File | Recipe | Size | Target |
|---|---|---|---|
LFM2.5-Encoder-350M-PII-Detector_wi8fc.tflite |
int8 dynamic-range (linears + embedding, convs float) | 364 MB | mobile + desktop |
LFM2.5-Encoder-350M-PII-Detector_fp16.tflite |
fp16 weights, float compute | 712 MB | desktop β full fidelity; phone memory limits (XNNPACK per-signature fp32 unpacking) |
Two signatures, pii_128 and pii_512 (S = 128 / 512, batch 1, right-padded):
| Tensor | Shape | Meaning |
|---|---|---|
input_ids |
int32 [1, S] |
token ids (the tokenizer prepends <|startoftext|>) |
attention_mask |
int32 [1, S] |
1 = real token, 0 = pad |
output_0 |
float32 [1, S, 161] |
BIOES logits, zeroed at padded positions |
Take the argmax per token, then decode the BIOES spans. The logit axis is 161 wide but only ids 0β108 are defined β label_schema.json carries the 109-entry id2label map (id 0 = O, "not personal information"); the remaining slots are unused. Restricting the argmax to the first 109 columns is the safe reading.
How to use
1. Install dependencies
pip install ai-edge-litert numpy tokenizers huggingface_hub
2. Save the script below as detect_pii.py:
#!/usr/bin/env python3
"""Tag personal information with litert-community/LFM2.5-Encoder-350M-PII-Detector."""
import argparse
import json
import numpy as np
from ai_edge_litert.interpreter import Interpreter
from huggingface_hub import hf_hub_download
from tokenizers import Tokenizer
REPO = "litert-community/LFM2.5-Encoder-350M-PII-Detector"
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--text", required=True, help="Text to scan.")
parser.add_argument("--seq-len", type=int, default=128, choices=[128, 512])
args = parser.parse_args()
model_path = hf_hub_download(REPO, "LFM2.5-Encoder-350M-PII-Detector_wi8fc.tflite")
tokenizer = Tokenizer.from_file(hf_hub_download(REPO, "tokenizer.json"))
schema = json.load(open(hf_hub_download(REPO, "label_schema.json")))
id_to_label = {int(i): name for i, name in schema["id2label"].items()}
encoding = tokenizer.encode(args.text)
if len(encoding.ids) > args.seq_len:
raise SystemExit(f"{len(encoding.ids)} tokens exceed --seq-len {args.seq_len}")
input_ids = np.zeros((1, args.seq_len), np.int32)
attention_mask = np.zeros((1, args.seq_len), np.int32)
input_ids[0, : len(encoding.ids)] = encoding.ids
attention_mask[0, : len(encoding.ids)] = 1
interpreter = Interpreter(model_path=model_path)
runner = interpreter.get_signature_runner(f"pii_{args.seq_len}")
logits = runner(input_ids=input_ids, attention_mask=attention_mask)["output_0"]
# Only ids 0..108 are defined in label_schema.json; the rest are unused slots.
tags = logits[0, : len(encoding.ids), : schema["num_labels"]].argmax(-1)
for token, tag in zip(encoding.tokens, tags):
if int(tag) != 0: # 0 = O, "not personal information"
print(f"{token:20} {id_to_label[int(tag)]}")
if __name__ == "__main__":
main()
3. Run it
python detect_pii.py --text "My email is jane@example.com and my phone number is 555-0142."
Δ jane B-contact.email
@example I-contact.email
.com E-contact.email
555 B-contact.phone
- I-contact.phone
014 I-contact.phone
2 E-contact.phone
On Android/iOS use the LiteRT runtime's SignatureRunner APIs with the same signature names; the tokenizer is the standard Hugging Face tokenizer.json, which the Rust/Swift/Kotlin tokenizers bindings all read.
Performance
One pass over a padded sequence with the int8 (wi8fc) file, CPU only.
| Device | Threads | pii_128 |
pii_512 |
|---|---|---|---|
| Apple M4 Max (macOS) | 8 | 34.9 ms | 111.9 ms |
| iPhone 17 Pro | 6 | 52 ms | not measured |
Mac figures are the median of 20 warm runs (ai-edge-litert 2.1.6, XNNPACK, otherwise idle machine). The iPhone figure comes from the on-device gate (TFLite C API + SignatureRunner + XNNPACK) and is a single run, not a median.
Budget for one slow first call. The first inference after loading pays a one-time graph preparation: on the Mac it took 376 ms against a 34.9 ms steady state. Later signatures on the same loaded model do not pay it again β pii_512 measured 110 ms cold against 112 ms warm. Model load itself was 0.38 s on the iPhone.
The signatures are fixed-shape, so the input language or content does not change the time β warm, pii_128 measures 36.4 / 37.3 / 36.6 ms on English, Japanese and Arabic sentences of 17, 21 and 27 tokens.
Accuracy note
Task-level parity against the PyTorch reference on a name + email + phone sentence: fp32 and fp16 reproduce the reference entity tags exactly. int8 keeps all multi-token spans (email, phone) intact and dropped exactly one tag in that test β an entity-end token whose fp32 decision margin was only 0.53 logits, a genuinely borderline call. That is the extent of what was checked; it is a single-sentence spot check, not a benchmark over a labelled corpus. If you need maximum recall on borderline tokens, use the fp16 file on desktop; on phones the int8 file is the artifact.
On the iPhone 17 Pro the int8 file reproduces the desktop outputs bit-exactly β cosine 1.000000, max absolute difference 0.0 over the full output tensor.
Android (Pixel 8a)
Android figures use the standard TFLite benchmark_model on a Pixel 8a (Tensor G3, Android 16) β 5 warm-up runs then 20 timed runs, the signature selected explicitly with --signature_to_run_for, CPU at 4 threads.
| Signature | GPU (OpenCL, previous export) | CPU (XNNPACK, 4 threads) |
|---|---|---|
pii_128 |
342 ms | 110 ms |
pii_512 |
1656 ms | 603 ms |
GPU status (2026-08-13 re-export): still CPU on mobile. The re-export respells the one idiom mobile GPU delegates refuse β transformers' rank-5 repeat_kv expand β into an equivalent rank-4 matmul (outputs bitwise-identical on CPU) β removing the family-wide GPU blocker β but this model's int8 file still does not compile on mobile GPU delegates: its 161-label classification head hits a runtime kernel limit ("failed to initialize kernel"), consistently on Metal and OpenCL. CPU remains the mobile path (bit-exact on device). The fp16 file does run under the GPU delegates with the head falling back to CPU and matches the fp32 reference (cosine 1.000000, desktop-verified) β a desktop option on GPU. On a Snapdragon it is the one file here that reaches an accelerator at all: 175.4 ms on a Galaxy S26 Hexagon NPU, from an ahead-of-time compile rather than the published file (see Snapdragon NPU (Hexagon) below).
Snapdragon NPU (Hexagon)
LFM2.5-Encoder-350M-PII-Detector_fp16.tfliteβ the NPU runs it at 175.4 ms. The GPU does not βLiteRtException: Failed to compile model.LFM2.5-Encoder-350M-PII-Detector_wi8fc.tfliteβ neither accelerator produced a usable row on the S26. Both ended the same way:LiteRtException: Failed to compile model.
| file | backend | compiled | inference (median / min) | load |
|---|---|---|---|---|
LFM2.5-Encoder-350M-PII-Detector_fp16.tflite |
NPU (Hexagon v81) | AOT (SM8850) | 175.4 ms / 171.8 ms | 445 ms |
Measured on a Samsung Galaxy S26 (Snapdragon 8 Elite Gen 5 / SM8850, Hexagon v81, Android 16) with LiteRT CompiledModel 2.2.0, one accelerator per process, 5 warm-up runs then N=50 timed runs, median reported. The run held thermal status NONE throughout. Headroom 0.76β0.80, where 1.0 is the throttling threshold.
The NPU row marked AOT ran an artifact compiled ahead of time for SM8850 (ai-edge-litert 2.2.0 + QAIRT 2.47.0), not the published file. That artifact is not distributed here; the compile is one command in the NPU guide.
GPU wiring: GPU guide.
License
LFM Open License v1.0 (see LICENSE, unchanged from the base model). Note the license's commercial-use threshold (Section 5). This repository redistributes converted Derivative Works of LiquidAI/LFM2.5-Encoder-350M-PII-Detector with modification notices per Section 4; all credit for the model to Liquid AI.
- Downloads last month
- 293
Model tree for litert-community/LFM2.5-Encoder-350M-PII-Detector
Base model
LiquidAI/LFM2.5-350M-Base