Download src/tools/probes.py from CIIL/Anuvani-Diarization: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/CIIL/Anuvani-Diarization/resolve/main/src/tools/probes.py
- Command line
-
hf download hf://CIIL/Anuvani-Diarization/src/tools/probes.py
-
curl -L -o probes.py https://huggingface.co/CIIL/Anuvani-Diarization/resolve/main/src/tools/probes.py
10.5 kB
| # Copyright (c) 2026 The Anuvani-Diarization authors | |
| # SPDX-License-Identifier: MIT OR Apache-2.0 | |
| """GPU probes (batching, voiceprints, devices): may the network side change anything without changing a single output | |
| bit? | |
| All models are built by the frozen release's own `load_bundle` (canonical flags: FP32, TF32 off, deterministic | |
| kernels, CUBLAS_WORKSPACE_CONFIG=:4096:8), on real IndicDiarBench audio and real cached masks. Every comparison is | |
| bitwise on the float32 outputs. Decision rules (written before running): | |
| batching a row's segmentation output must not change when other rows' content or its position in the batch | |
| change (fixed batch shape); batch-size dependence is reported separately. Cross-file batching is | |
| allowed only if every row is bit-identical across all tested batch sizes. | |
| voiceprints the repacked voiceprint trunk (fbank + ResNet trunk once per window, in batches of 8 distinct windows; | |
| pooling and seg_1 at the reference rows and shapes) is allowed only if every final embedding is | |
| bit-identical. | |
| devices concurrent CUDA streams in one process, and each GPU, must reproduce GPU 0's sequential bits; with | |
| --occupy the reference is repeated with that fraction (for example 0.95) of GPU memory occupied before | |
| the first forward (cuDNN engine fallback). | |
| source env/canonical.sh && CUDA_VISIBLE_DEVICES=0 python -s tools/probes.py batching --out artifacts/probes | |
| """ | |
| import argparse | |
| import hashlib | |
| import json | |
| import os | |
| import sys | |
| import threading | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| PROJ = Path(__file__).resolve().parents[1] | |
| REL = PROJ.parent / "anuvani_diar_v1" | |
| SID = PROJ.parents[1] | |
| sys.path.insert(0, str(PROJ / "tests")) | |
| WIN, STEP = 256000, 25600 | |
| # golden files (3 languages) | |
| FILES = ["bengali_009", "hindi_033", "kannada_035"] | |
| def load_ref(): | |
| import refpkg | |
| ref = refpkg.load() | |
| from anuvani_diar_ref.frontend import load_bundle | |
| return ref, load_bundle | |
| def audio(sid): | |
| import soundfile as sf | |
| p = next((SID / "benchmark_data/indicdiar_full").glob(f"*/{sid}.flac")) | |
| a, sr = sf.read(str(p), dtype="float32") | |
| assert sr == 16000 and a.ndim == 1 | |
| return a | |
| def windows(a, n): | |
| """First n reference windows (chunk i starts at i * 25600; zero-padded if short).""" | |
| out = np.zeros((n, 1, WIN), np.float32) | |
| for i in range(n): | |
| seg = a[i * STEP:i * STEP + WIN] | |
| out[i, 0, :len(seg)] = seg | |
| return out | |
| def masks_for(sid): | |
| import records | |
| p = next(p for s, p in records.all_records() if p.stem == sid) | |
| return np.load(p)["masks"] | |
| def used_weights(masks): | |
| """pyannote get_embeddings(exclude_overlap=True) mask choice: clean frames if more than 2, else the full mask.""" | |
| m = masks.astype(np.float64) | |
| clean = m * (m.sum(axis=2, keepdims=True) < 2) | |
| w = np.where((clean.sum(axis=1, keepdims=True) > 2), clean, m) | |
| # (C, 799, 4) | |
| return np.nan_to_num(w).astype(np.float32) | |
| def sha(t): | |
| return hashlib.sha256(t.detach().cpu().contiguous().numpy().tobytes()).hexdigest()[:16] | |
| def seg_forward(model, x, torch): | |
| with torch.inference_mode(): | |
| return torch.softmax(model(torch.from_numpy(x).to("cuda")), -1).cpu() | |
| def emb_parts(emb_model, torch, F): | |
| r = emb_model.resnet | |
| def trunk(w): | |
| fb = emb_model.compute_fbank(w) | |
| x = fb.permute(0, 2, 1) | |
| x = x.unsqueeze_(1) | |
| out = F.relu(r.bn1(r.conv1(x))) | |
| return r.layer4(r.layer3(r.layer2(r.layer1(out)))) | |
| def head(out, weights): | |
| return r.seg_1(r.pool(out, weights=weights)) | |
| return trunk, head | |
| def batching(args): | |
| import torch | |
| import torch.nn.functional as F | |
| ref, load_bundle = load_ref() | |
| model = load_bundle(REL / "bundle", "cuda:0", 8, 2) | |
| seg = model["pipeline"]._segmentation.model | |
| emb = model["pipeline"]._embedding.model_ | |
| trunk, head = emb_parts(emb, torch, F) | |
| res = dict(torch=torch.__version__, cudnn=torch.backends.cudnn.version(), gpu=torch.cuda.get_device_name(0), | |
| deterministic=torch.are_deterministic_algorithms_enabled(), segmentation={}, voiceprints={}) | |
| rng = np.random.default_rng(0) | |
| for sid in FILES: | |
| a = audio(sid) | |
| W = windows(a, 24) | |
| # reference: a full batch of 8, in order | |
| base = seg_forward(seg, W[:8], torch) | |
| seg_res = res["segmentation"][sid] = {} | |
| perm = rng.permutation(8) | |
| seg_res["position_change"] = bool(torch.equal(seg_forward(seg, W[:8][perm], torch)[np.argsort(perm)], base)) | |
| # other rows replaced by other windows | |
| mixed = W[:8].copy(); mixed[1::2] = W[8:16][1::2] | |
| seg_res["other_rows_content_change"] = bool(torch.equal(seg_forward(seg, mixed, torch)[0::2], base[0::2])) | |
| zero = W[:8].copy(); zero[1::2] = 0.0 | |
| seg_res["other_rows_zeroed"] = bool(torch.equal(seg_forward(seg, zero, torch)[0::2], base[0::2])) | |
| seg_res["batch_size_identical"] = {} | |
| for b in (1, 2, 3, 4, 5, 6, 7, 9, 12, 16, 24): | |
| Wb = W[:b] if b <= 24 else W | |
| ob = seg_forward(seg, Wb, torch) | |
| k = min(b, 8) | |
| seg_res["batch_size_identical"][b] = bool(torch.equal(ob[:k], base[:k])) | |
| # voiceprints, reference rows = 2 chunks x 4 speakers (identical crops), real weights | |
| m = used_weights(masks_for(sid)) | |
| vp_res = res["voiceprints"][sid] = {} | |
| with torch.inference_mode(): | |
| # three reference batches | |
| for start in (0, 2, 6): | |
| crops = torch.from_numpy(np.repeat(W[start:start + 2], 4, axis=0)).to("cuda") | |
| wts = torch.from_numpy(np.stack([m[start + i // 4, :, i % 4] for i in range(8)])).to("cuda") | |
| # what the release computes | |
| ref_emb = emb(crops, weights=wts) | |
| # 8 distinct windows, same shape | |
| distinct = torch.from_numpy(W[start:start + 8]).to("cuda") | |
| T = trunk(distinct) | |
| # gather the two chunks' rows, x4 each | |
| Tg = T[[0, 0, 0, 0, 1, 1, 1, 1]].contiguous() | |
| rep = head(Tg, wts) | |
| T_ref = trunk(crops) | |
| vp_res[f"batch_{start}"] = dict(embedding_identical=bool(torch.equal(rep, ref_emb)), | |
| trunk_rows_identical=bool(torch.equal(T_ref[0], T[0]) and torch.equal(T_ref[4], T[1])), | |
| max_abs_diff=float((rep - ref_emb).abs().max())) | |
| # slot positions: the same window at each of the 8 slots of a distinct batch | |
| pos_ok = True | |
| for slot in range(8): | |
| batch = W[8:16].copy(); batch[slot] = W[0] | |
| T = trunk(torch.from_numpy(batch).to("cuda")) | |
| T0 = trunk(torch.from_numpy(np.repeat(W[0:1], 8, axis=0)).to("cuda")) | |
| pos_ok &= bool(torch.equal(T[slot], T0[0])) | |
| vp_res["trunk_all_slot_positions"] = pos_ok | |
| # tail: the zero-padded last chunk and digital silence | |
| # a short, zero-padded window | |
| tail = windows(a[: 5 * STEP + 70000], 6)[-1:] | |
| silent = np.zeros((1, 1, WIN), np.float32) | |
| for name, w in (("padded_tail", tail), ("digital_silence", silent)): | |
| ref8 = trunk(torch.from_numpy(np.repeat(w, 8, axis=0)).to("cuda")) | |
| mix = W[8:16].copy(); mix[3] = w[0] | |
| got = trunk(torch.from_numpy(mix).to("cuda")) | |
| vp_res[f"trunk_{name}"] = bool(torch.equal(got[3], ref8[0])) | |
| out = Path(args.out); out.mkdir(parents=True, exist_ok=True) | |
| (out / "batching.json").write_text(json.dumps(res, indent=1)) | |
| print(json.dumps(res, indent=1)) | |
| def devices_reference(args): | |
| """Sequential reference digests on the visible GPU (also with most of its memory occupied, when --occupy is set).""" | |
| import torch | |
| import torch.nn.functional as F | |
| if args.occupy: | |
| free, total = torch.cuda.mem_get_info() | |
| # held for the run | |
| hog = torch.empty(int(free * args.occupy) // 4, dtype=torch.float32, device="cuda") | |
| ref, load_bundle = load_ref() | |
| model = load_bundle(REL / "bundle", "cuda:0", 8, 2) | |
| seg = model["pipeline"]._segmentation.model | |
| emb = model["pipeline"]._embedding.model_ | |
| dig = {} | |
| for sid in FILES: | |
| W = windows(audio(sid), 16) | |
| m = used_weights(masks_for(sid)) | |
| with torch.inference_mode(): | |
| dig[f"{sid}/seg"] = sha(seg_forward(seg, W[:8], torch)) | |
| crops = torch.from_numpy(np.repeat(W[0:2], 4, axis=0)).to("cuda") | |
| wts = torch.from_numpy(np.stack([m[i // 4, :, i % 4] for i in range(8)])).to("cuda") | |
| dig[f"{sid}/emb"] = sha(emb(crops, weights=wts)) | |
| if args.streams: | |
| # two threads, each on its own CUDA stream, running the same batches concurrently | |
| outs = {} | |
| def work(name, sid): | |
| W = windows(audio(sid), 16) | |
| s = torch.cuda.Stream() | |
| with torch.cuda.stream(s), torch.inference_mode(): | |
| for _ in range(3): | |
| o = torch.softmax(seg(torch.from_numpy(W[:8]).to("cuda", non_blocking=False)), -1) | |
| s.synchronize() | |
| outs[name] = sha(o) | |
| ts = [threading.Thread(target=work, args=(f"t{i}", FILES[i % len(FILES)])) for i in range(4)] | |
| [t.start() for t in ts]; [t.join() for t in ts] | |
| dig["streams"] = {k: v == dig[f"{FILES[int(k[1:]) % len(FILES)]}/seg"] for k, v in outs.items()} | |
| gpu = os.environ.get("CUDA_VISIBLE_DEVICES", "?") | |
| out = Path(args.out); out.mkdir(parents=True, exist_ok=True) | |
| tag = f"devices_gpu{gpu}" + (f"_occupy{args.occupy}" if args.occupy else "") + ("_streams" if args.streams else "") | |
| (out / f"{tag}.json").write_text(json.dumps(dig, indent=1)) | |
| print(tag, json.dumps(dig)) | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("what", choices=["batching", "devices"]) | |
| ap.add_argument("--out", default=str(PROJ / "artifacts/probes")) | |
| ap.add_argument("--occupy", type=float, default=0.0) | |
| ap.add_argument("--streams", action="store_true") | |
| a = ap.parse_args() | |
| if os.environ.get("CUBLAS_WORKSPACE_CONFIG") != ":4096:8" or not sys.flags.no_user_site: | |
| raise SystemExit("source env/canonical.sh and run with python -s") | |
| batching(a) if a.what == "batching" else devices_reference(a) | |
| if __name__ == "__main__": | |
| main() | |