r/deeplearning • u/PatronusProtect • 13m ago
One encoder, seven heads: what we learned training a unified security classifier with masked losses
We spent the last months consolidating seven separate sequence classifiers into one multi-head model, our apex model, so to speak, and since the weights are now public, I wanted to share what worked and what surprised us.
Setup: a shared mmBERT-small encoder with seven task heads, binary injection (BCE), document class (7-way), tool type (14-way), tool operation (6-way), tool data-flow tags (3× BCE, multi-label), intent routing (5-way), and threat type (7-way).
The part that needed care: our training rows only carry labels for a subset of tasks, so absent tasks are masked out of the loss entirely. We ended up writing a self-test that asserts absent-task gradients are exactly zero, which caught two subtle bugs, and I'd recommend it to anyone doing similar masking. About 5k synthetic/real multi-task rows help the heads co-train; the test sets stay 100 % real data.
Held-out results per head: injection F1 0.962, documents 0.980, tool type 0.957, tool operation 0.945, tool tags 0.958, routing 0.916, threat 0.952.
Quantization: both the unified model and the dedicated single-task variants ship quantized -edge builds (ONNX INT8 + INT4 embeddings, from 96 MB) with measured parity benchmarks in the repos, the worst head loses 0.012 against FP32.
Was it worth it vs. seven dedicated models? We released both variants, so you can judge for yourself, the dedicated models score marginally higher on most tasks, but the unified one does one encoder pass instead of up to seven.
Our weak spot: routing, at 0.916. The intent classes overlap semantically ("write code that analyzes my data" is that code or analytics?), and I suspect the ambiguity is genuinely in the data. If you have ideas beyond relabeling, let me know :)
Weights and per-head metrics: https://huggingface.co/patronus-studio