DPDA-GA-TCM / implementation-notes.md
korallll's picture
Wrapper code (speech-spoof-bench)
552e29f verified
|
Raw History Blame Contribute Delete
3.93 kB
# DPDA-GA-TCM — implementation notes
Plan: `speech-spoof-bench/docs/plans/2026-10-29-mamba-batch-arena-submission.md` (model #2).
## Source
- Code: github.com/ductuantruong/dpda_ga @ `aab89614ee90252e10e5df35ec6aaa5ee2e179a7` (MIT, `LICENSE.source`):
`nets/conformer.py` + `nets/conformer_tcm.py` → `_net.py` (edits listed in its header).
- Weights: HF `tuantd/dpda_ga` @ `531554e0a1c28d6a6ccf22a355281e3d3acefeb5`,
`pcgrad/conformer_tcm/best/best_{0..4}.pth` (downloaded by `benchmarks/_ckpt_dl/dl.py`).
- Hyper-parameters = `main.py` defaults: emb 144, heads 4, kernel 31, 4 encoders; checkpoint
shapes confirm (LL 144×1024, ht_proj 144×36, depthwise conv k=31, encoder_blocks 0..3).
- XLS-R 300M architecture built from `xlsr2_300m.pt` cfg (symlink to `../W2V2-AASIST/`) via
`merge_with_parent`; base weights not loaded (the checkpoint overwrites all 588 tensors).
## Checkpoint averaging (`make_avg.py` → `dpda_ga_tcm_avg4.pth`)
- `best_4.pth` is truncated on HF: 547,680,256 bytes vs 1,285,107,746 for the others, no ZIP
central directory; `torch.load` → "failed finding central directory". Confirmed locally.
- `main.py` eval averages `n_average_model` (default 5) n-best checkpoints, but
`sd = model.state_dict()` aliases the live parameters, so the following
`model.load_state_dict(best_1)` overwrites `sd` in place. Net effect:
`(2*b1 + b2 + … + b_{n-1}) / n`; best_0 never contributes. Maintainer decision (2026-10-30):
reproduce main.py exactly with the 4 intact files → `(2*best_1 + best_2 + best_3) / 4`,
float32, main.py's summation order; verified bit-identical to literally running main.py's
loop on a module holding the tensors. Int buffers (num_batches_tracked) get the truncated
float quotient, as main.py's copy into a long buffer does (unused at eval).
- The paper's numbers come from the 5-file version `(2*b1+b2+b3+b4)/5`, so small deviations
from the paper are expected.
## Wrapper decisions
- Input: first 66,800 samples; shorter clips repeat-tiled (`utils.pad`). No resampling/normalisation.
- Score = `logits[:, 1]` (label 1 = bonafide in `utils.read_metadata`; `produce_evaluation_file`).
- `.to(device)` then `.eval()`; `extract_feat`'s `self.model.train()` removed.
- cuDNN TF32 disabled in `load()`: with it on, batched vs single scores differed by up to 2e-4
(XLS-R conv front-end); off → 1.7e-6. 4070 Ti SUPER speed unchanged (121.7 utt/s @24 both ways);
5060 Ti PyTorch drops 47.5 → 22.2 utt/s (bs 4), but that card runs TensorRT.
- Params: 321,213,220 (incl. the frozen 10000×144 positional table, 1.44 M).
## Batch sweep (sweep.py, 66,800-sample random input, 2026-10-30)
| card | TF32 off best | TF32 on best |
|---|---|---|
| RTX 4070 Ti SUPER (idx 2) | 121.7 utt/s @24 | 121.7 @24 |
| RTX 5060 Ti (idx 3) | 22.2 @4 | 47.5 @4 |
Raw: `logs_sweep_g{2,3}_tf{0,1}.txt`.
## Sanity (full J-SPAW_LA, PyTorch, RTX 4070 Ti SUPER)
- EER 12.90% (2397 trials, 0 skipped), ~110 utt/s. Comparable XLS-R models: DPDA-GA-AASIST 13.65,
XLSR-SLS 8.70, W2V2-AASIST 26.17. Not flipped.
## TensorRT (`trt_dpda_ga_tcm.py`, sidecar `trt_dpda-ga-tcm.json`, TRT 10.16)
Adapted from XLSR-MamBo's script (config block only). The class-token loop rewrite (`_net.py`
edit 4) keeps the batch axis dynamic in the traced ONNX.
| 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 (FP16 Spearman 0.99963 < 0.9999) | 8 | 173.3 | 1.0000 | 12.8992 (=PT) | 0.999998 / 0.031 |
| RTX 5060 Ti (idx 3) | FP16 | 16 | 362.2 | 0.99991 | 12.8992 (=PT) | 0.999917 / 0.287 |
Both cards pass (|ΔEER| = 0 ≤ 0.25, Spearman ≥ 0.999). J-SPAW_LA is submitted from PyTorch scores
(`results_pt/`); TRT runs in `results_trt/` (idx 2) and `results_trt3/` (idx 3). `cmp_scores.py`
does the comparison. The unused 4070 FP16 engine was deleted.