DeepFense-CodecFake-W2V2-MLP / implementation-notes.md
korallll's picture
Wrapper code (speech-spoof-bench)
5182c89 verified
|
Raw History Blame Contribute Delete
5.64 kB
# DeepFense-CodecFake-W2V2-MLP β€” implementation notes
Plan: `speech-spoof-bench/docs/plans/2026-11-07-eleven-model-batch-arena-submission.md` (model #8).
## Source
- Weights: HF `DeepFense/CodecFake_Wav2Vec2_MLP_NoAug_Seed42` @ `c08bd2125c83e6c8fdc64566bfbfda18dff4dfb4`,
`best_model.pth` (3.79 GB, sha256 `1d4bf44afd0f7e887266c8dc49875b0ddb60a07fb16279fa2d56598519ea3c1a` = HF LFS oid)
+ `config.yaml`, in `benchmarks/_ckpt_dl/DeepFense__CodecFake_Wav2Vec2_MLP_NoAug_Seed42/`. The .pth is a
training checkpoint: `model_state` (435 fp32 tensors, 317,522,306 params), `optimizer_state` (Adam),
`epoch`=2, `step`=46298, `best_metric`=0.00306 (val EER fraction). No weight license on the Hub β†’ linked, not re-hosted.
- Extraction: `torch.load(weights_only=True)` works (plain tensors/ints) β€” run once with `python -I`; `model_state`
saved unchanged as `model_state.safetensors` (sha256 `5c97bd4ae3a2075aa8df309bde8b7dd7f9ce540a50df969d4ff0ba7a74a560a5`).
The optimizer state is dropped.
- Code: `github.com/Yaselley/deepfense-framework` @ `3f2fe1146c48ea4bacc480dd96d806b6c72c09cf` (Apache-2.0), checked out
to `benchmarks/_ckpt_dl/Yaselley__deepfense-framework/` (not imported by the wrapper; only by `source_parity.py`).
## Architecture (`_net.py`)
- `frontend.model`: fairseq 0.12.2 `Wav2Vec2Model` built from the vendored `xlsr2_300m_model_cfg.json`
(= `state["cfg"]["model"]` of the local `W2V2-AASIST/xlsr2_300m.pt`, merged onto `Wav2Vec2Config` defaults with
`merge_with_parent`, as in W2V2-AASIST/_net.py). No .pt is loaded at runtime. Called as
`model(x, mask=False, features_only=True)["x"]` (last layer, after the encoder's final layer_norm β€” layer_norm_first).
- `backend.projection_block` = Linear(1024,128) β†’ LayerNorm(128) β†’ ReLU; mean over time (pool "mean").
- `losses.0.fc` = Linear(128, 2).
- Key mapping: the checkpoint was saved from real fairseq (it carries `quantizer.*`, `project_q.*`, `final_proj.*`),
so all 435 tensors map 1:1 β†’ `strict=True`, no allow-list. Those 7 pre-training tensors are unused on the
features_only path. (The repo's newer `fairseq_local` loader lacks those modules: strict=False reports exactly
those 7 as unexpected, 0 missing.) params_millions 317.522306 counts them (as the checkpoint does).
## Preprocessing / score (`deepfense_codecfake_w2v2_mlp.py`)
- `StandardDataset` + `transforms.load_audio` / `pad` (test split: max_len 64000, pad_type repeat, random_pad false):
soundfile float, mono mean, no normalisation anywhere (dataset, frontend: XLS-R `normalize: false`), first 64000
samples, shorter β†’ `np.tile(x, ceil(64000/len))[:64000]`. Empty clip β†’ zeros (not in source). No resampling. FP32,
cuDNN TF32 off.
- Score = logit1 βˆ’ logit0 (label_map bonafide=1), as specified in the plan; monotone in softmax p(bonafide), which
is what the authors' `simple.infer.py` thresholds. **Deviation note:** the authors' `test.py`/trainer EER uses
`CrossEntropy.get_score` = `logits[:, 1]` only (its docstring says "logit_bonafide βˆ’ logit_spoof" but the code
returns the single logit). The two are not rank-equivalent; see the alt-score table below.
- **Source parity** (`source_parity.py`, log `logs_source_parity.txt`): authors' `ModularDetector` (fairseq_local
frontend from xlsr2_300m.pt, strict=False load of model_state) + their `load_audio`/`pad` vs this wrapper on
12 clips (6 J-SPAW_LA, 6 In-the-Wild): max |Ξ” logits| = 4.8e-6.
## Batch sweep (`sweep.py`, 64000-sample input, TF32 off, 2026-11-07; `logs_sweep_g{1,2}.txt`)
| card | best | notes |
|---|---|---|
| RTX 4070 Ti SUPER (idx 2) | 123.9 utt/s @24 | plateau 16–64 |
| RTX 5060 Ti (idx 1) | 48.5 utt/s @4 | ~33 at bs β‰₯ 8 β†’ batch_size 4 on 5060 |
## Sanity (PyTorch, RTX 5060 Ti idx 1)
- Full J-SPAW_LA (`results_pt/J-SPAW_LA`, copied to `results/J-SPAW_LA` for submission): EER **0.625%**
(2397 trials, 0 skipped), ~46 utt/s end-to-end. Low but not inverted; Codecfake training data is codec
re-synthesis, J-SPAW_LA spoofs are apparently easy for it.
- Full ODSS (`results_pt/ODSS`): EER **13.17%** (26,954 trials) vs self-reported 11.51% (+1.66 pt). The Arena ODSS
build may differ from the authors' ODSS protocol (e.g. VCTK spoof-only subset, generator mix); not investigated further.
- Alt score (`alt_score_eer.py`, `logs_alt_score.txt`): authors' test.py score `logit1` vs ours `logit1 βˆ’ logit0`:
J-SPAW_LA 0.625 / 0.625, ODSS 13.20 / 13.17 β†’ the score choice does not explain the ODSS gap and is immaterial.
## TensorRT (`trt_deepfense_codecfake_w2v2_mlp.py`, sidecar `trt_deepfense-codecfake-w2v2-mlp.json`, TRT 10.16.1.11)
Adapted from Forensics-XLSR-Wild's script; `_ExportNet` returns (B,2), n_classes 2. FP16 fails the 0.9999
build-parity rule on both cards (Spearman 0.99899 on 4070, 0.99940 on 5060, 64 ITW clips) β†’ FP32 on both
(FP16 engines deleted).
| card | precision | best batch | utt/s (sweep) | build parity Spearman (64 ITW) | J-SPAW EER | J-SPAW Spearman vs PT / max Ξ” |
|---|---|---|---|---|---|---|
| RTX 4070 Ti SUPER (idx 2) | FP32 | 32 | 182.4 | 0.99991 | 0.625 (=PT) | 0.999980 / 0.0223 |
| RTX 5060 Ti (idx 1) | FP32 | 8 | 126.0 | 0.99995 | 0.625 (=PT) | 0.999983 / 0.0317 |
Both pass (|Ξ”EER| = 0, Spearman β‰₯ 0.999). Scripts: `run_pt.sh`, `run_trt_export.sh`, `run_trt_jspaw.sh <gpu>`, `run_alt.sh`.
## Hosting
- `.gitignore` excludes `model_state.safetensors` and `*.onnx` (unlicensed weights must not be re-hosted).
`meta.yaml` `checkpoint` links the original HF best_model.pth @ c08bd21.
- `trained_on` omitted (Codecfake is not an Arena dataset). Paper block = arXiv 2604.08450 (kheir2026deepfense).