Spaces:
Running on Zero
Running on Zero
File size: 5,233 Bytes
c0494a9 c0306c8 c0494a9 e04f90f c0494a9 e04f90f c0494a9 c0306c8 c0494a9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | import sys
sys.stdout.reconfigure(line_buffering=True)
try:
import spaces
except ImportError:
# keep @spaces.GPU usable as a no-op; ZeroGPU requires this exact name.
class spaces:
class GPU:
def __init__(self, func=None, duration=60):
self.func = func
def __call__(self, *args, **kwargs):
if self.func is not None:
return self.func(*args, **kwargs)
func = args[0]
return func
import os
import threading
import gradio as gr
import torch
from pyharp import ModelCard, build_endpoint, load_audio
from models.moe_research.w2v2_moe_fz24_aasist import Model
from utils.tools.tools import pad
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
CKPT_PATH = os.path.join(os.path.dirname(__file__), "checkpoints", "fz24_moe_aasist.ckpt")
TRUNCATE = 64600 # ~4s at 16kHz (default: T=64600 samples, per paper)
model = None
model_ready = False # has the device-resident model been built yet?
model_loading = True
model_error = None
def build_model():
"""Builds the detector fresh: frozen wav2vec2 backbone from the HF Hub plus
the trained mixture-of-experts fusion and AASIST head from the checkpoint.
The checkpoint only ever stores the MoE/AASIST weights, never the backbone,
since the backbone is frozen and untouched during training."""
detector = Model()
state_dict = torch.load(CKPT_PATH, map_location="cpu", weights_only=False)["state_dict"]
state_dict = {k[len("model."):]: v for k, v in state_dict.items() if k.startswith("model.")}
detector.load_state_dict(state_dict, strict=False)
detector.eval()
return detector
def warm_cache():
"""Downloads the wav2vec2 backbone and does a dry-run build on CPU, so a
bad checkpoint or a Hub outage surfaces before the first real request
instead of mid-inference. CPU only -- ZeroGPU only intercepts CUDA calls
made inside an @spaces.GPU call, not from a background thread."""
global model_loading, model_error
try:
build_model()
print("Backbone + checkpoint warmed.")
except Exception as e:
model_error = str(e)
print(f"Load error: {e}")
finally:
model_loading = False
threading.Thread(target=warm_cache, daemon=True).start()
model_card = ModelCard(
name="FAD-MoE",
description=(
"Detects AI-generated (spoofed) speech. A frozen wav2vec 2.0 backbone's "
"24 layers are fused by a sparse mixture-of-experts, then classified by "
"an AASIST graph-attention head. Only the first ~4 seconds of the clip "
"are analyzed, per the paper."
),
author=(
"Zhiyong Wang, Ruibo Fu, Zhengqi Wen, Jianhua Tao, Xiaopeng Wang, "
"Yuankun Xie, Xin Qi, Shuchen Shi, Yi Lu, Yukun Liu, Chenxing Li, Xuefei Liu"
),
tags=["deepfake-detection", "speech", "anti-spoofing"],
)
def preprocess(sig):
"""Pad/crop to ~4s, raw waveform, no z-score norm -- matches
asvspoof_data_DA.py, the data module this checkpoint's hparams.yaml
actually names. Mono/16kHz resample added since HARP users upload
arbitrary files, unlike the original's pre-formatted 16kHz mono
ASVspoof set."""
sig = sig.to_mono().resample(16000)
wav = sig.audio_data[0, 0].cpu().numpy()
wav = pad(wav, TRUNCATE)
return torch.tensor(wav, dtype=torch.float32)
@spaces.GPU
@torch.inference_mode()
def process_fn(input_audio_path: str) -> str:
"""Runs the detector on one clip and writes a bonafide/spoofed verdict with
confidence scores to a text file."""
global model, model_ready
if model_loading:
raise gr.Error("Model is still loading, please wait a moment and try again.")
if model_error is not None:
raise gr.Error(f"Model failed to load: {model_error}")
if not model_ready:
model = build_model().to(DEVICE)
model_ready = True
sig = load_audio(input_audio_path)
wav = preprocess(sig).unsqueeze(0).to(DEVICE)
pred, _, _ = model(wav)
probs = torch.nn.functional.softmax(pred, dim=1)[0]
bonafide_prob = probs[1].item()
spoof_prob = probs[0].item()
verdict = "Likely genuine" if bonafide_prob >= spoof_prob else "Likely AI-generated / spoofed"
input_name = os.path.basename(input_audio_path)
out_path = os.path.splitext(input_audio_path)[0] + "_result.txt"
with open(out_path, "w") as f:
f.write(f"{input_name}\n\n")
f.write("AI-Detection Result\n")
f.write(
f"{verdict} (genuine confidence: {bonafide_prob:.1%}, "
f"spoofed confidence: {spoof_prob:.1%})\n"
)
return out_path
with gr.Blocks() as demo:
input_components = [
gr.Audio(type="filepath", label="Input Audio").harp_required(True),
]
output_components = [
gr.File(type="filepath", label="Detection Result").set_info(
"Bonafide/spoofed verdict with confidence scores."
),
]
build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
if __name__ == "__main__":
demo.queue().launch(pwa=True)
|