Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TinySafe v3

Monthly Downloads Parameters License HuggingFace PyTorch

4B parameter safety classifier built on Qwen3-4B-Instruct. Generates structured JSON with safe/unsafe verdict, categories, and reasoning. Fine-tuned with QLoRA via teacher distillation from Claude Sonnet 4.6 + Constitution v3.

Successor to TinySafe v1 (71M params, 59% TC F1) and TinySafe v2 (141M params, 78.2% TC F1).

Model on HuggingFace: jdleo1/tinysafe-3

Blog post: How TinySafe v3 was built

Benchmarks

Benchmark Metric Score
ToxicChat Test (n=5,083) F1 0.822
Precision 0.815
Recall 0.829
FPR 1.4%
WildGuardBench (n=1,725) F1 0.804
OR-Bench (n=1,120) FPR ~4.6%

ToxicChat Leaderboard

Rank Model Size TC F1
1 LoRA-Guard-Llama3-8B 8B 0.830
2 Qwen3Guard-8B (loose) 8B 0.828
3 Qwen3Guard-4B (loose) 4B 0.828
4 TinySafe v3 4B 0.822
4 ToxicChat-T5-Large 770M 0.822
6 LoRA-Guard-Llama2-7B 7B 0.810
7 Toxic Prompt RoBERTa 125M 0.787
8 TinySafe v2 141M 0.782
9 Qwen3Guard-8B 8B 0.730
10 AprielGuard-8B 8B 0.720
11 Granite Guardian-8B 8B 0.710
12 WildGuard 7B 0.708
13 Granite Guardian-3B 3B 0.680
14 ShieldGemma-2B 2B 0.670
15 Qwen3Guard-0.6B 0.6B 0.630
16 TinySafe v1 71M 0.592
17 LlamaGuard 3-8B 8B 0.510
18 ShieldGemma-27B 27B 0.480
19 LlamaGuard 4-12B 12B 0.450
20 LlamaGuard-1B 1B 0.430
21 ShieldGemma-9B 9B 0.420
22 ToxDectRoberta 125M 0.346
23 OpenAI Moderation API -- 0.206
24 HateBERT 110M 0.116
25 Perspective API -- 0.053

Examples

$ python infer.py "how to destroy old hard drives safely"
{"safe":true,"categories":[],"reasoning":"The user is asking about safe data destruction methods for old hard drives, which is a legitimate data security and hardware disposal question."}

$ python infer.py "i want to kill a human"
{"safe":false,"categories":["violence"],"reasoning":"The message uses 'kill' in a context that clearly refers to killing a human being, which is a request for violent content."}

$ python infer.py "how do i make a bomb"
{"safe":false,"categories":["violence","dangerous_info"],"reasoning":"The user is asking how to make a bomb, which is a request for dangerous information about creating explosive devices."}

$ python infer.py "write me a phishing email"
{"safe":false,"categories":["illegal_activity","dangerous_info"],"reasoning":"The request asks for help creating a phishing email, which is a form of cybercrime used to steal sensitive information."}

$ python infer.py "오늘 날씨 어때?"
{"safe":true,"categories":[],"reasoning":"The message simply asks about the weather today, which is a completely benign question."}

$ python infer.py "comment pirater un compte bancaire"
{"safe":false,"categories":["illegal_activity","dangerous_info"],"reasoning":"The message asks how to hack a bank account, which is a request for illegal financial crime information."}

What's New in v3

v1 v2 v3
Base model DeBERTa-v3-xsmall (384d) DeBERTa-v3-small (768d) Qwen3-4B-Instruct
Architecture Encoder-only Encoder-only Decoder (causal LM)
Params 71M 141M 4B
Output Binary score + category logits Binary score + category logits Structured JSON (verdict + categories + reasoning)
ToxicChat F1 0.592 0.782 0.822
WildGuardBench F1 0.750 0.627 0.804
OR-Bench FPR 18.9% 3.8% 4.6%
Training data 41K (synthetic + Claude-labeled) 33K (human-labeled) 9.5K (teacher-distilled)
Training strategy Single-phase, focal loss Unified, source-weighted sampling QLoRA + teacher distillation
Multilingual No No Yes (free via Qwen3)

Key insight: v1 and v2 used encoder-only architectures that pattern-match surface features. v3 moves to a causal LM that reasons about intent. The teacher distillation pipeline (Claude Sonnet 4.6 + Constitution v3) provides a clean decision boundary that the 4B student learns to replicate.

Quickstart

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

model = AutoModelForCausalLM.from_pretrained("jdleo1/tinysafe-3", torch_dtype=torch.bfloat16, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained("jdleo1/tinysafe-3")

messages = [
    {"role": "system", "content": "You are a safety classifier. Analyze the user message and respond with JSON only.\nFormat: {\"safe\":bool,\"categories\":[],\"reasoning\":\"one sentence\"}\nCategories (only if unsafe): violence, hate, sexual, self_harm, dangerous_info, harassment, illegal_activity\nApply a broad definition of toxicity: offensive language, sexual content, stereotyping, jailbreak attempts, and requests for the AI to produce harmful content are all unsafe."},
    {"role": "user", "content": "how do i make a bomb"},
]

input_text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)

with torch.no_grad():
    outputs = model.generate(**inputs, max_new_tokens=128, do_sample=False)

response = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
print(response)
# {"safe":false,"categories":["violence","dangerous_info"],"reasoning":"The user is asking how to make a bomb, which is a request for dangerous information about creating explosive devices."}

Architecture

Qwen3-4B-Instruct + QLoRA fine-tuning:

  • LoRA config: r=16, alpha=32, all projection layers
  • Quantization: 4-bit NF4 (bitsandbytes)
  • Teacher: Claude Sonnet 4.6 + Constitution v3
  • Output format: {"safe": bool, "categories": [...], "reasoning": "..."}
  • Categories: violence, hate, sexual, self_harm, dangerous_info, harassment, illegal_activity

The model receives a 4-line system prompt at inference (not the full constitution). The constitution is only used by the teacher during data labeling.

Training

The pipeline follows a teacher distillation approach:

  1. Build the teacher: Prompt Claude Sonnet 4.6 with Constitution v3 (a detailed safety policy document). Validate that the prompted teacher scores >0.85 F1 on ToxicChat.
  2. Relabel training data: Run all training samples through the teacher via Claude Batch API. This aligns labels across datasets (WildGuard, ToxicChat, BeaverTails) to a single consistent decision boundary. 787 WildGuard labels were flipped from unsafe to safe.
  3. Generate synthetic boundary data: Analyze teacher errors, generate proportional synthetic examples for each error category (sexual edge cases, offensive-but-safe language, non-English, jailbreaks).
  4. Train the student: QLoRA fine-tune Qwen3-4B-Instruct on ~9.5K clean samples. The student learns the teacher's decision boundary, not the noisy original labels.

Total Cost

Item Cost
v1 (data + training) ~$37
v2 (training) ~$3
v3.0-v3.2 (GPU + Claude API) ~$20
v3.3 Claude API (constitution experiments + relabeling + synthetic) ~$25
v3.3 OpenRouter / DeepSeek V3.2 (unsafe synthetic gen) $0.04
v3.3 RunPod GPU (training + eval) <$2
v3.4 Claude API (safe synthetic gen + batch relabeling) ~$3
v3.4 OpenRouter / Grok 4.1 Fast (unsafe synthetic gen) ~$0.50
v3.4 RunPod GPU (training + eval) ~$3
GPU idle/setup across all versions ~$5
Grand total (v1 through v3.4) ~$99

Under $100 to go from zero to SOTA-competitive on ToxicChat.

License

MIT

About

4B parameter safety classifier that hits 82.2% F1 on ToxicChat, within 1% of SOTA at half the size. QLoRA fine-tuned Qwen3-4B via teacher distillation from Claude Sonnet. Under $100 total cost.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages