Cravatar NSFW Avatar Content Moderation Strategy — From Gravatar Rating to In-House Detection

AI Infrastructure Perspective: Model Selection Revision + PoC Plan + Deployment Strategy

Overall support for this proposal. From the AI infrastructure side, we’ll supplement implementation-level details and revise the model selection.

1. Model Selection: Recommend Marqo over NudeNet

I previously conducted a detailed comparison in “Cravatar Avatar NSFW Moderation: Model Selection & Deployment Recommendations”, concluding that Marqo/nsfw-image-detection-384 is preferable to NudeNet v3:

NudeNet v3 Falconsai Marqo (Recommended)
Input resolution 320×320 224×224 384×384
Architecture YOLO-based ViT-base-patch16-224 ViT-base-patch16-384
Primary use case Explicit region detection (bounding box) General NSFW binary classification NSFW binary classification, optimized for search/classification scenarios
Misclassification on anime/artistic avatars High Moderate Lower — better performance on ambiguous (“gray zone”) cases
Maintenance status Last updated long ago Actively maintained Actively maintained

Core rationale for choosing Marqo:

  • The 384×384 input resolution aligns better with avatar use cases: downsampling from 512×512 incurs minimal quality loss; upscaling from small sizes (e.g., 80×80) preserves more detail.
  • NudeNet is a detection model (outputs bounding boxes + explicit-region classification), whereas our use case only requires binary classification (safe/unsafe). A dedicated classifier is simpler and more appropriate.
  • Marqo’s team has specifically optimized their model for image search and classification tasks — resulting in lower misclassification rates on edge cases.
  • Deployment complexity is identical: just swap the model name.

Hardware requirements are also lighter: FP16 model weights ~172 MB; peak memory usage (including activations) ~500–800 MB. With ONNX Runtime instead of PyTorch, total memory can be further reduced to ~800 MB, while inference speed improves 2–3×.

2. I’ll Run the PoC

Plan:

  • Sample 2,000 avatars from Cravatar’s existing cache, stratified by access frequency: high-frequency (500), medium-frequency (500), long-tail (500), plus known edge-case images (500).
  • Run all three models — Marqo, Falconsai, and NudeNet v3 — on the same dataset to let data speak for itself.
  • Record per-image confidence score, predicted label, and inference latency.
  • Produce a comparative report covering: accuracy, recall, confidence distribution, gray-zone ratio, and average inference speed.
  • Pay special attention to the categories highlighted by @wenpai: anime/2D avatars, pixel-art avatars, text-only avatars, and low-resolution blurry images.

This PoC also resolves threshold calibration: real-world confidence distributions will inform rational gray-zone boundaries (e.g., safe/unsafe thresholds), rather than arbitrary guesses like 0.3–0.95.

The ailab environment (64 GB RAM + CPU) handles this scale effortlessly — results expected within 1–2 hours. I’ll need @wenpai to either provide the sampled dataset or grant me access to the cache directory.

3. Deployment Strategy: Reuse Vector API’s Architecture

A robust FastAPI + containerized service pattern is already running stably on ailab (e.g., the Vector API has been live for months). We’ll reuse the same architecture for the NSFW detector:

nsfw-detector/
├── Dockerfile
├── app/
│   ├── main.py          # FastAPI entry point
│   ├── detector.py      # Model loading & inference logic
│   └── preprocessor.py  # Image preprocessing pipeline
└── models/              # Model files (downloaded at build time — not committed to git)

API design:

POST /detect
Content-Type: multipart/form-data
Body: file=<image>

Response:
{
  "safe": true,
  "confidence": 0.97,
  "model": "marqo-nsfw-384",
  "inference_ms": 45,
  "preprocessed_size": "384x384"
}

POST /detect/batch     // For bulk scanning
Content-Type: application/json
Body: {"urls": ["https://...", ...]}

Container resource limits: 2 CPUs + 4 GB RAM. Managed via systemd --user, with auto-start on boot.

4. Image Preprocessing Pipeline

Critical to detection accuracy — fully encapsulated inside the service; callers need not handle it:

  1. Format normalization: Convert all inputs (WebP/JPEG/GIF) to RGB PNG.
  2. Upscaling: Resize to 384×384 (Marqo’s required input size) using Lanczos interpolation.
  3. GIF handling: Extract first/middle/last frames; if any frame is unsafe, the entire GIF is flagged unsafe.
  4. Alpha channel handling: For PNGs with transparency, composite onto white background before inference.
  5. Fallback handling: Corrupted files, zero-byte inputs, or non-image formats → flag as “review” and route to human moderation queue.

5. Deployment Location

Agree with @wenpai: avoid co-locating with the PHP origin server. Two options:

  • Option A: Dedicated container in the same data center as the origin — internal network call latency < 1 ms. Ideal for synchronous checks (e.g., before writing to R2).
  • Option B: Deployed on ailab — public API latency ~50–200 ms. Suitable for asynchronous checks.

We’ll initially deploy the PoC on ailab. Once model performance is validated, we’ll finalize production deployment location.

6. Additional Suggestion: Vector Similarity for NSFW Deduplication

ailab already hosts semantic search infrastructure (ChromaDB + bge-m3). We can extract embedding vectors from confirmed unsafe avatars and build an NSFW vector database. When new avatars are uploaded, perform a similarity search against this database: any match above a cosine similarity threshold (e.g., > 0.95) is automatically flagged unsafe — bypassing full model inference.

This is especially effective against repeated uploads of the same NSFW image by different users. Prioritize this after the three-layer architecture (preprocessing → inference → post-processing) is production-ready.


Summary: Switch model recommendation from NudeNet to Marqo/nsfw-image-detection-384. I’ll run the PoC — just share the stratified sample data.