Download source_parity.py from SpeechAntiSpoofingBenchmarks/LaST-Net: direct link, hf CLI and curl.
- Browser
- Download file 3.89 kB
-
https://huggingface.co/SpeechAntiSpoofingBenchmarks/LaST-Net/resolve/main/source_parity.py
- Command line
-
hf download hf://SpeechAntiSpoofingBenchmarks/LaST-Net/source_parity.py
-
curl -L -o source_parity.py https://huggingface.co/SpeechAntiSpoofingBenchmarks/LaST-Net/resolve/main/source_parity.py
3.89 kB
| """Source parity: the authors' LaST-Net code (model.py LaSTNet + load_dataset.read_audio/crop_or_repeat + the infer.py | |
| scoring loop, one clip at a time) vs this wrapper (batched), on J-SPAW_LA clips, at 4 s (Arena) and 6 s (authors' default). | |
| python source_parity.py <LaST-Net clone @ 3838f0c> [n_clips] | |
| Deviations from running infer.py itself (both are loading-only, not math): | |
| * eval.load_model does torch.load(best.pt, weights_only=False) -> not allowed here; the authors' LaSTNet instead | |
| loads the same tensors from last_net_model.safetensors (= best.pt["model"], extract_state.py) with strict=True. | |
| * fairseq.checkpoint_utils.load_model_ensemble_and_task([xlsr2_300m.pt]) fails on fairseq 0.12.2 (stale task cfg | |
| keys) -> patched to build the same Wav2Vec2Model from the vendored xlsr2_300m model cfg; every tensor is then | |
| overwritten by the strict load anyway. | |
| TF32 off on both sides (FP32). | |
| """ | |
| import io | |
| import os | |
| import sys | |
| import tempfile | |
| import numpy as np | |
| import pyarrow.parquet as pq | |
| import soundfile as sf | |
| import torch | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| sys.path.insert(0, HERE) | |
| src = sys.argv[1] | |
| n = int(sys.argv[2]) if len(sys.argv) > 2 else 16 | |
| import _net # noqa: E402 | |
| import last_net as W # noqa: E402 | |
| import fairseq # noqa: E402 | |
| fairseq.checkpoint_utils.load_model_ensemble_and_task = lambda paths, **k: ([_net.build_xlsr()], None, None) | |
| sys.path.append(src) | |
| import model as src_model # noqa: E402 | |
| from load_dataset import SAMPLE_RATE, crop_or_repeat, read_audio # noqa: E402 | |
| torch.backends.cudnn.allow_tf32 = False | |
| torch.backends.cuda.matmul.allow_tf32 = False | |
| ref = src_model.LaSTNet(ssl_path="<patched>", temperature=0.8, uniform_floor=0.05, layer_dropout=0.10) | |
| r = ref.load_state_dict(W.load_state(), strict=True) | |
| print("source strict=True: missing", r.missing_keys, "unexpected", r.unexpected_keys) | |
| ref = ref.cuda().eval() | |
| ours = W.LaSTNetModel() | |
| ours.load() | |
| t = pq.read_table(os.path.join(HERE, "..", "J-SPAW_LA", "data", "test-00000-of-00001.parquet"), | |
| columns=["audio", "label"]).to_pylist() | |
| rows = [x for x in t if x["label"] == 0][: n // 2] + [x for x in t if x["label"] == 1][: n - n // 2] | |
| auds = [sf.read(io.BytesIO(x["audio"]["bytes"]), dtype="float32")[0] for x in rows] | |
| auds += [auds[0][:20000], auds[1][:5000], np.concatenate([auds[2]] * 3)] # short (repeat) + long (crop) cases | |
| tmp = tempfile.mkdtemp() | |
| paths = [] | |
| for k, a in enumerate(auds): | |
| p = os.path.join(tmp, f"{k}.wav") | |
| sf.write(p, a, 16000, subtype="FLOAT") # lossless float container for the authors' path-based reader | |
| paths.append(p) | |
| print("lens", [len(a) for a in auds]) | |
| for seconds in (4, 6): | |
| samples = seconds * SAMPLE_RATE | |
| theirs = [] | |
| with torch.inference_mode(): | |
| for p in paths: # infer.py main() loop, verbatim | |
| wav = read_audio(p) | |
| available = min(len(wav), samples) / SAMPLE_RATE | |
| batch = torch.from_numpy(crop_or_repeat(wav, samples)).unsqueeze(0).to("cuda") | |
| duration = torch.tensor([available], device="cuda") | |
| logits = ref(batch, duration).float() | |
| theirs.append(logits.log_softmax(dim=1)[0, 1].item()) | |
| theirs = np.array(theirs) | |
| if seconds == 4: | |
| mine = np.array(ours.score_batch(auds, [16000] * len(auds))) | |
| else: # same wrapper code path with the 6 s window | |
| with torch.no_grad(): | |
| xs, av = zip(*(_net.crop_np(a, samples) for a in auds)) | |
| lg = ours.net(torch.from_numpy(np.stack(xs)).cuda(), torch.tensor(av, dtype=torch.float64).cuda()).float() | |
| mine = lg.log_softmax(dim=1)[:, 1].cpu().numpy() | |
| print(f"--- {seconds} s ({samples} samples), n={len(auds)}") | |
| print("source", np.round(theirs, 4).tolist()) | |
| print("ours ", np.round(np.asarray(mine, dtype=np.float64), 4).tolist()) | |
| print(f"max_abs_diff_{seconds}s", float(np.max(np.abs(mine - theirs)))) | |