GPU-backed HTTP wrapper around two PII models plus co-hosted Gemma and GLM chat models in one Tinfoil enclave:
screenpipe/pii-redactor(v50_distilled6l) — six-layer, vocab-pruned XLM-R token classifier for text PII. EndpointPOST /filter.screenpipe/pii-image-redactor(rfdetr_v38) — RF-DETR-Nano detector for image PII in screenshots. Large desktop frames use the same whole-frame + four-tile policy as the desktop app. EndpointPOST /image/detect.google/gemma-4-E2B-it— chat + vision + audio via vLLM. EndpointPOST /v1/chat/completionswith the backwards-compatible API model id"gemma4-e4b". Weights are baked into the image (~10 GB BF16).patrickbdevaney/GLM-5.3-Flash-REAP50-GGUF— 50%-expert-pruned GLM-5.3-Flash, served from the 67.2 GiBIQ3_Mquant by a pinned CUDAllama-server. EndpointPOST /glm/v1/chat/completions.
Note: the 31B model (
gemma4-31b) is not in this container. It stays on Tinfoil's hostedinference.tinfoil.shendpoint, reached separately bypackages/ai-gatewayin the Cloudflare worker. The co-hosted E2B is a smaller multimodal-audio model, not a drop-in replacement.
All four workloads deploy inside the same Tinfoil confidential-compute enclave on one H200 (~141 GB VRAM) so neither pixels, text, nor chat prompts leave an attested runtime. The existing privacy-filter container remains the only public router: /v1/* goes to Gemma over loopback and /glm/v1/* goes to the isolated GLM sibling over an enclave-only bridge. One TLS-attested URL, one auth token, four workloads sharing one allocation.
GET /health → {"status": "ok", "model_ready": true, "image_model_ready": true,
"pii_gpu_ready": true, "text_provider": "CUDAExecutionProvider", ...}
POST /filter → {"text": "My email is alice@foo.com"}
← {"redacted": "My email is [EMAIL]",
"spans": [{"label": "private_email", "start": 12, "end": 25,
"text": "alice@foo.com", "score": 0.99}],
"latency_ms": 180,
"model": "screenpipe/pii-redactor:v50_distilled6l (mixed-int4-int8-onnx)"}
POST /image/detect → {"image_b64": "<b64-jpg-or-png>", "threshold": 0.30}
← {"detections": [{"bbox": [x, y, w, h], "label": "private_person", "score": 0.95},
{"bbox": [x, y, w, h], "label": "secret", "score": 0.91}],
"latency_ms": 95, "model": "rfdetr_v38",
"width": 2880, "height": 1800}
POST /glm/v1/chat/completions
→ {"model": "glm-5.3-flash-reap50-iq3m",
"messages": [{"role": "user", "content": "Hello"}]}
← OpenAI-compatible chat-completion response
Bbox is [x, y, w, h] in original-image pixel space (the server un-resizes from the model's 512×512 input). Labels are the canonical 12-class screenpipe PII taxonomy.
# build
docker build -t privacy-filter:dev .
# run
docker run --rm -p 8080:8080 privacy-filter:dev
# smoke test
curl -s http://localhost:8080/health
curl -s -X POST http://localhost:8080/filter \
-H 'Content-Type: application/json' \
-d '{"text":"Call Alice at +1 415 555 0100 about alice@example.com"}' | jq
# Image: send a JPG/PNG as base64.
B64=$(base64 -i some_screenshot.png)
curl -s -X POST http://localhost:8080/image/detect \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg img "$B64" '{image_b64: $img, threshold: 0.30}')" | jqThe first build downloads the v50 text contract (~133 MB across model, tokenizer, config, and vocabulary remap), the 60 MB rfdetr_v38 ONNX, and the baked Gemma weights. Subsequent builds use the build cache. Both HuggingFace revisions and every model artifact checksum are pinned, so a rebuild cannot silently drift to different weights.
-
Build and push the image to GitHub Container Registry using the
privacy-filter releaseworkflow. Supply the semantic version without thevprefix (for example0.9.0). The workflow prints the immutable router image digest. Build the GLM runtime with its separate workflow; it publishes under the existing publicprivacy-filterpackage.gh workflow run release.yml --ref <branch> -f version=0.9.0 gh workflow run glm-runtime.yml --ref <branch> -f version=0.9.0
-
Pin the digest in
tinfoil-config.yml, commit it, then create the matchingv*tag. The tag runs the Tinfoil measurement/attestation job.git add tinfoil-config.yml git commit -m "release: privacy-filter v0.9.0" git tag v0.9.0 && git push origin <branch> v0.9.0
-
Update the existing deployment through Tinfoil's blue-green update flow. Stage the candidate when you want to exercise it before promotion; the current instance stays live unless the candidate becomes healthy and is accepted.
-
Verify — the service is now reachable at
https://privacy-filter.<org>.containers.tinfoil.dev/health.
Text model (v50_distilled6l) — mixed int4/int8 ONNX:
| Metric | Value |
|---|---|
| Model artifact | 114 MB |
| Model shape | six-layer XLM-R student |
| Runtime window | 256 tokens with 64-token overlap |
| Local CPU reference | 7–9 ms for short captured strings |
Image model (rfdetr_v38) — CUDA EP:
| Metric | Value |
|---|---|
| Weights (FP16, FP32 I/O) | 60 MB |
| Params | ~25 M |
| Input resolution | 512×512 |
| Inference policy | whole frame + four overlapping tiles for large desktops |
| Real captured-frame core recall | 81.2% for email/phone/secret with tiling (62.5% whole-frame only) |
| Real-screen false-positive audit | 0 clear false positives across the held-out 240-frame set at 0.50 |
The deployed CVM also carries Gemma E2B and its vLLM working set, so
tinfoil-config.yml remains the source of truth for total CPU, memory, and GPU sizing.
GLM-5.3-Flash REAP50 (IQ3_M) — llama.cpp/CUDA:
| Metric | Value |
|---|---|
| Quantized model | 67.2 GiB |
| Parameters after expert pruning | about 165B (from 321B) |
| Runtime context | 65,536 tokens total, two 32,768-token parallel slots |
| GPU offload | all layers on the H200 |
| Prefill batches | 4,096 logical / 1,024 physical tokens |
| Decode acceleration | ngram-mod self-speculation (16-token match, 4–32-token drafts) |
The model and runtime are pinned by exact commits. Tinfoil mounts the measured model package read-only; the model is not downloaded at runtime.
- Tinfoil remote attestation covers the exact image digest, so clients can verify the specific model bits + server code that handled their request.
- Only the paths listed in
tinfoil-config.ymlare exposed — Tinfoil's shim allowlist blocks every other URL at the enclave boundary. - Model weights are immutable. The text, image, and Gemma artifacts are baked into the router image. GLM is a measured Tinfoil model package mounted read-only. No workload downloads weights at runtime.
- The exact container image is measured and attested. Tinfoil runs the router and co-hosted vLLM as the artifact-owning
appuser(10001:999) with access to the injected NVIDIA device; the shim and CVM remain the external security boundary.
- Text: English-primary. Multilingual coverage per upstream model card varies.
- Text: long inputs are split into overlapping 256-token windows; request size is capped separately by
MAX_INPUT_CHARS. - Image: v38 materially improves measured real-screen precision and tiled recall, but unusual layouts, scripts, handwriting, occlusion, and adversarial inputs still need their own evaluation.
- Image: payload cap
MAX_IMAGE_BYTES=20 MB(override via env). Decoded RGB working buffer is ~3× the payload. - GLM: the REAP50
IQ3_Mbuild trades quality for fitting beside the existing workloads on one H200. Validate it on your own tasks before replacing a larger hosted model. - Not a compliance certification. One layer in a privacy-by-design stack.
PolyForm Noncommercial License 1.0.0. You can read, run, fork, modify, and share — noncommercial use only. Commercial use requires a separate license; reach out to louis@screenpi.pe.
Bundled / downloaded model weights keep their own licenses: screenpipe/pii-redactor, screenpipe/pii-image-redactor (rfdetr_v38), google/gemma-4-E2B-it (Gemma Terms of Use), and patrickbdevaney/GLM-5.3-Flash-REAP50-GGUF (MIT). Using this deployment commercially means complying with all of them in addition to this repo's license.