SafetyCard / app.py
coralexbadea's picture
SafetyCard v0.1.0: free safety report card for Hugging Face LLMs
e69a1c5 verified
Raw History Blame Contribute Delete
4.84 kB
"""SafetyCard web app (Gradio). Instant file scan runs on a free CPU Space; deep scan needs a GPU
(run this app locally on a machine with a GPU)."""
import glob
import json
import os
import gradio as gr
import pandas as pd
import torch
from safetycard import __version__, report
HAS_GPU = torch.cuda.is_available()
ALLOW_CPU_DEEP = os.environ.get("SAFETYCARD_ALLOW_CPU", "0") == "1"
RESULTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "results")
SPACE_ID = os.environ.get("SPACE_ID", "NAMESPACE/SafetyCard") # set automatically on HF Spaces
LOCAL_RUN = (f"git clone https://huggingface.co/spaces/{SPACE_ID} && cd SafetyCard\n"
"pip install -r requirements.txt\n")
def instant_scan(repo_id):
repo_id = (repo_id or "").strip()
if not repo_id:
return "Enter a model id, e.g. `Qwen/Qwen2.5-1.5B-Instruct`.", None
try:
card = report.build(repo_id, deep=False)
except Exception as e:
return f"Could not scan `{repo_id}`: {e}", None
return report.to_markdown(card), card
def deep_scan(repo_id, base_id):
if not (HAS_GPU or ALLOW_CPU_DEEP):
return ("No GPU on this Space. Run the deep scan free on your own GPU:\n\n"
"```bash\n" + LOCAL_RUN + "python -m safetycard.cli scan " + (repo_id or "org/model") +
" --deep --out card.json\n```\nor run `python app.py` on a machine with a GPU."), None
repo_id = (repo_id or "").strip()
try:
card = report.build(repo_id, base_id=(base_id or "").strip() or None, deep=True)
except Exception as e:
return f"Deep scan failed for `{repo_id}`: {e}", None
path = os.path.join("/tmp", repo_id.replace("/", "__") + ".safetycard.json")
report.save(card, path)
return report.to_markdown(card), path
def leaderboard():
rows = []
for p in sorted(glob.glob(os.path.join(RESULTS_DIR, "*.json"))):
try:
c = json.load(open(p))
except Exception:
continue
s, g = c.get("safety", {}), c.get("lineage", {})
rows.append({"Model": c["model"], "File grade": c["files"]["grade"],
"Parent": c.get("parent_model") or "",
"Lineage": g.get("verdict", ""), "Weight change": g.get("overall_rel_change", ""),
"Safety d′": s.get("dprime_mean", ""),
"d′ range": f"{s.get('dprime_min', '')}–{s.get('dprime_max', '')}" if s else "",
"vs parent": s.get("vs_parent", ""), "Version": c.get("version", ""),
"Scanned": c.get("scanned_at", "")[:10]})
return pd.DataFrame(rows)
ABOUT = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "ABOUT.md")).read() \
if os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "ABOUT.md")) else ""
with gr.Blocks(title="SafetyCard: free safety report card for Hugging Face LLMs") as demo:
gr.Markdown(f"# 🛡️ SafetyCard\n**A free safety report card for any Hugging Face language model.** "
f"Check file safety in seconds, then run deep checks on your own GPU. v{__version__}")
with gr.Tab("⚡ Instant file scan"):
gr.Markdown("Inspects the repo's files and metadata. **Downloads no weights**, so it is safe and fast.")
rid = gr.Textbox(label="Model id", placeholder="Qwen/Qwen2.5-1.5B-Instruct")
btn = gr.Button("Scan files", variant="primary")
out_md = gr.Markdown()
out_json = gr.JSON(label="Raw card")
btn.click(instant_scan, rid, [out_md, out_json])
with gr.Tab("🔬 Deep scan (your GPU)"):
gr.Markdown("Compares weights with the parent model and measures the model's internal safety "
"structure. Safetensors only, never `trust_remote_code`, no text generated."
+ ("" if HAS_GPU else "\n\n**This Space has no GPU.** Run it on a GPU machine, "
"or clone it and run locally (instructions appear when you click)."))
rid2 = gr.Textbox(label="Model id")
base2 = gr.Textbox(label="Parent model (optional, default: declared base_model)")
btn2 = gr.Button("Run deep scan", variant="primary")
out2 = gr.Markdown()
file2 = gr.File(label="Your SafetyCard JSON (submit it to the leaderboard)")
btn2.click(deep_scan, [rid2, base2], [out2, file2])
with gr.Tab("🏆 Leaderboard"):
gr.Markdown("Community-submitted SafetyCards. Add yours by opening a pull request with your "
"`card.json` in `results/` on this Space.")
table = gr.Dataframe(leaderboard(), interactive=False, wrap=True)
gr.Button("Refresh").click(leaderboard, None, table)
with gr.Tab("❓ How it works"):
gr.Markdown(ABOUT)
if __name__ == "__main__":
demo.launch()