Skip to content

[RNE Rewrite] feat: add privacy filter pipeline#1321

Open
msluszniak wants to merge 6 commits into
rne-rewritefrom
@ms/rewrite-privacy-filter
Open

[RNE Rewrite] feat: add privacy filter pipeline#1321
msluszniak wants to merge 6 commits into
rne-rewritefrom
@ms/rewrite-privacy-filter

Conversation

@msluszniak

@msluszniak msluszniak commented Jul 20, 2026

Copy link
Copy Markdown
Member

Description

Ports the privacy filter (token-level PII detection) to the new TypeScript-orchestrated architecture.

  • createPrivacyFilter — validates the forward signature against the configured label space, pre-allocates the static window tensors, and runs sliding windows with 50% overlap (no truncation), keeping the more centred prediction near window boundaries.
  • privacyFilterUtils — BIOES grammar construction, constrained Viterbi decode, span extraction.
  • usePrivacyFilter hook, models.privacyFilter registry (openai/nemotron × XNNPACK/MLX), BIOES label constants.
  • Privacy filter demo screen in the NLP example app.

Introduces a breaking change?

  • Yes
  • No

Type of change

  • Bug fix (change which fixes an issue)
  • New feature (change which adds functionality)
  • Documentation update (improves or adds clarity to existing documentation)
  • Other (chores, tests, code style improvements etc.)

Tested on

  • iOS
  • Android

Testing instructions

  1. cd apps/nlp && yarn ios (or yarn android).
  2. Open the Privacy Filter screen.
  3. Pick a model chip (OpenAI 8 types / Nemotron 55 types; MLX variants are iOS-only) and tap Detect PII.
  4. Detected spans are highlighted inline and listed with label and token range.

Related issues

Closes #1246

Checklist

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation accordingly
  • My changes generate no new warnings

Additional notes

Viterbi decode runs in O(numLabels + numEntities) per token instead of O(numLabels²), cutting a ~27% CPU overhead on Nemotron's large (221) label space.

Port the privacy filter (token-level PII detection) to the new
TypeScript-orchestrated architecture.

The current implementation runs windowing, constrained Viterbi decoding and
span extraction in C++ (`models/privacy_filter/`). The transformer forward
pass dominates the inference budget, so per the Amdahl's-law rule all of that
moves to TypeScript, leaving the native layer untouched.

- `createPrivacyFilter` task: validates the `forward` signature against the
  configured label space, pre-allocates the static window tensors, and runs
  sliding windows with 50% overlap (no truncation), keeping the more centred
  prediction near window boundaries.
- `privacyFilterUtils`: BIOES grammar construction, constrained Viterbi
  decode, and span extraction.
- `usePrivacyFilter` hook, `models.privacyFilter` registry entries
  (openai/nemotron, XNNPACK + MLX), and the BIOES label constants.
- Privacy filter demo screen in the NLP example app.

Closes #1246
@msluszniak msluszniak self-assigned this Jul 20, 2026
@msluszniak msluszniak added refactoring feature PRs that implement a new feature labels Jul 20, 2026
@msluszniak msluszniak linked an issue Jul 20, 2026 that may be closed by this pull request
On-device profiling showed the dense transition loop dominated the
non-native cost for large label spaces:

  openai   ( 33 labels): viterbi   6ms /  710ms total —  ~1% of detect
  nemotron (221 labels): viterbi 264ms /  970ms total — ~27% of detect

`model.execute` costs the same ~705ms for both models, so the entire gap
was the decode: 44x more time for a 6.7x bigger label set, i.e. the
expected N^2 blowup.

Under BIOES the max over predecessors collapses into a few shared group
maxima — every `O`/`B-`/`S-` target sees the same two candidates (best
background state, best `E-`/`S-` state), and each `I-x`/`E-x` has exactly
two predecessors (`B-x`, `I-x`). Storing the grammar as those groups
rather than an N x N matrix makes each step O(numLabels + numEntities).

Nemotron decode drops 264ms -> 5ms on device (~27% -> ~0.9% of detect,
total 970ms -> 708ms), keeping the pipeline in TypeScript rather than
pushing it into C++.

Verified equivalent: identical-score paths vs the previous dense
implementation across both label spaces, two bias sets and lengths 1-512,
and still optimal + grammar-valid against a brute-force reference.

Also declare the privacyFilter feature in the NLP example app so its
native backend is provisioned.
Comment thread apps/nlp/app/privacy-filter/index.tsx Outdated
Comment thread apps/nlp/app/privacy-filter/index.tsx Outdated
Comment thread apps/nlp/app/privacy-filter/index.tsx Outdated
Comment thread packages/react-native-executorch/src/extensions/nlp/tasks/privacyFilter.ts Outdated
Comment thread packages/react-native-executorch/src/extensions/nlp/tasks/privacyFilter.ts Outdated
The privacy filter ships MLX iOS variants (OPENAI.MLX_INT4, NEMOTRON.MLX_INT8)
but the feature-to-backend map only requested xnnpack, so MLXBackend.xcframework
was never downloaded or force-loaded and the MLX delegate failed at execute.
Point the privacy filter at v0.10.0, whose xnnpack exports carry a
get_dynamic_dims_forward companion. When present, each sliding window is sized
to the tokens it holds (bucketed to 32) rather than padded to the full window.
The token-classification MoE runs every expert on every token, so inference is
linear in sequence length and short inputs run ~4.6x faster on device. Static
models without the companion keep the single full-window tensor unchanged.
…terminal

- extractSpans now breaks at BIOES openers (B-/S-) so two adjacent same-type
  entities no longer merge into one span.
- viterbiDecode gains `constrainEnd`; the final window is forced to close on a
  valid terminal (O/E/S) so a sequence cannot end on an open B-/I-. Interior
  windows stay unconstrained (their boundary tokens are discarded/re-decoded).
@msluszniak
msluszniak marked this pull request as ready for review July 23, 2026 15:06
@msluszniak
msluszniak requested a review from barhanc July 23, 2026 15:06
@msluszniak

Copy link
Copy Markdown
Member Author

This one needs to implement input shape validation on JS side, so it will wait till the PR with input validation. However, the core of the PR might be reviewed cc: @barhanc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature PRs that implement a new feature refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RNE Rewrite] Add Privacy Filter pipeline implementation

1 participant