# ProSDD-ASV19 — implementation notes Plan: `speech-spoof-bench/docs/plans/2026-11-07-eleven-model-batch-arena-submission.md` (model #6). ## Source - Code: github.com/ProSDD/codes @ `bdb80440cd946368e77f4b51f6ddf26c7443dc33` — `model_stage2realfake.py` (`ProSDDStage2`), `main_eval.py` (`inference_forward`), `data_utils_eval.py` (`pad`). No LICENSE file (→ `LICENSE.source`). `Evaluation_scores/ProSDD_*Trained/readme.txt` are empty: no per-utterance author scores. - Weights: authors' Google Drive folder `1h250-Um5qWo-rpeOE6K_Gdeygy46k3xg`, file `ProSDD_Trained2019` (id `1RHY-VELyi4XH-L1egeRcgWjHdkdWV2bO`), fetched with gdown into `benchmarks/_ckpt_dl/ProSDD/`; 1.27 GB, sha256 `d2cb857cf7b14a9caa200cde14100062e72fe2a39865bc435f83f5be74da1c8a`. No extension; it is a zip-format torch save of a plain `OrderedDict` state_dict (431 tensors, no `module.` prefix) → loads with `torch.load(weights_only=True)`; `ProSDD_Trained2019` here is a symlink. No license → NOT re-hosted (meta links Drive). - Backbone config vendored as `xlsr300m_config.json` (facebook/wav2vec2-xls-r-300m @ 1a640f3, copied from Forensics-XLSR-Wild); `Wav2Vec2Model(config)` instead of `from_pretrained` (no Hub download). ## Wrapper (`prosdd_asv19.py:ProSDDASV19`, `_net.py:ProSDDNet`) - `_net.ProSDDNet.forward` = `inference_forward` verbatim: `ssl.feature_extractor` → transpose → `ssl.feature_projection` (tuple[0]) → latent truncated / zero-padded to `T_target=200` (64000 samples give 199 frames, so 1 zero frame is always appended) → `ssl.encoder(attention_mask=None)` → mean over all 200 frames → `cls_head` Linear(1024,512)-ReLU-Dropout-Linear(512,2). classifier_pool = mean (checkpoint has no `attn`/`attn_q` tensors). - Preprocessing = `data_utils_eval.pad`: 64000 samples; longer → centre crop `s=(n-64000)//2`; shorter → zero pad `left=(64000-n)//2`, rest right. No amplitude normalisation, no resampling. Empty clip → zeros (source would also give zeros). FP32, TF32 off (cuDNN + matmul). - Score = `logits[:,1]` (class 1 = bona fide in the source) → higher = bona fide. - Strict load: checkpoint keys outside `ssl.*`/`cls_head.*` must equal the allow-list {`mask_embed`, `pros_ln.weight/bias`, `final_proj.weight/bias`} (Stage-I masked-prediction token, prosody-target LN, 1024→448 speaker+prosody head; all training-only), asserted; then `load_state_dict(strict=True)`: 0 missing / 0 unexpected. `ssl.masked_spec_embed` is loaded (it is a Wav2Vec2Model parameter) but unused at inference. The source's own `strict=False` load into `ProSDDStage2` reports 0 missing / 0 unexpected. - Params: 315,964,546 total; **315,963,522 forward path** (minus masked_spec_embed, 1024) → meta 315.963522. - **Source parity** (`source_parity.py `): authors' ProSDDStage2 (from_pretrained swapped for the vendored config) + `main_eval.inference_forward` + `data_utils_eval.pad`, per clip, vs the wrapper batched, on 16 J-SPAW clips + a 20000-sample and a 139944-sample clip: max |Δ logit| = 1.0e-5 (batching noise). - Unit tests `test_prosdd_asv19.py`: pad/crop, strict load, eval mode + param count, 199→200 frames, batch == single (atol 1e-3), determinism, bona fide > spoof on J-SPAW clips. 8 passed. ## Batch sweep (`sweep.py`, 64000-sample input, 2026-11-07; run with the ASV19 checkpoint, same graph for both) | card | best | notes | |---|---|---| | RTX 4070 Ti SUPER (idx 2) | 126.2 utt/s @24 | plateau 16–64 → `batch_size = 24` | | RTX 5060 Ti (idx 0) | 47.8 utt/s @4 | ~32 at bs ≥ 8 (card shared with another job during the sweep) → 4 on 5060 | ## Sanity (PyTorch, RTX 5060 Ti idx 0) - Full J-SPAW_LA: EER **24.734%** (2397 trials, 0 skipped). Not ~50 / not inverted. - ASVspoof2019_LA quick check (`subset_eer.py 2000`, 2000 clips sampled with seed 0 from eval shard 0): EER 0.449% (2000 clips: 223 bona fide / 1777 spoof) vs paper 0.42%. - Training data (paper): Stage I LibriSpeech train-clean-100 + dev (bona fide only); Stage II ASVspoof 2019 LA train/dev, RawBoost 3. Arena ASVspoof sets are eval partitions → no trained_on entries. ## TensorRT (`trt_prosdd_asv19.py`, sidecar `trt_prosdd-asv19.json`, TRT 10.16.1.11) Adapted from Forensics-XLSR-Wild's script: `_ExportNet` returns the (B,2) logits; n_classes taken from the traced output; ONNX file named per card (`prosdd-asv19_.onnx`) so the two cards can export concurrently (only the 4070 copy kept). Build parity on 64 InTheWild clips; FP16 kept iff Spearman ≥ 0.9999. | card | precision | best batch | utt/s (sweep) | build Spearman | J-SPAW EER | J-SPAW Spearman vs PT / max Δ | |---|---|---|---|---|---|---| | RTX 4070 Ti SUPER (idx 2) | FP32 (FP16 build Spearman 0.99977 < 0.9999) | 32 | 186.5 | 1.0 | 24.734 (=PT) | 0.999999 / 0.0164 | | RTX 5060 Ti (idx 0) | FP32 (FP16 build Spearman 0.99986 < 0.9999) | 8 | 61.5 | 1.0 | 24.734 (=PT) | 0.999999 / 0.0172 | Both cards pass (|ΔEER| ≤ 0.25, Spearman ≥ 0.999). Unused FP16 engines deleted. J-SPAW_LA submitted from PyTorch scores (`results_pt/` → `results/`); TRT runs in `results_trt2/`, `results_trt0/`; compare with `cmp_scores.py`. Scripts: `run_pt.sh`, `run_trt_export.sh `, `run_trt_jspaw.sh `.