File size: 3,718 Bytes
63dd946 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | # Copyright (c) 2026 The Anuvani-Diarization authors
# SPDX-License-Identifier: MIT OR Apache-2.0
"""Export the clustering constants the Rust engine needs as raw little-endian arrays, from the release bundle.
DiariZen's `vbx_setup` recomputes the PLDA whitening for every file with scipy's `eigh` (scipy's own OpenBLAS). It
depends only on the bundle's plda.npz, so it is computed here once, with the canonical environment, and stored
bit-exactly. The same computation is repeated with 64 OpenBLAS threads in a subprocess; any array whose bits differ is
recorded in the manifest (measured 7 Oct 2026: plda_tr_T and plda_psi differ, i.e. the Python release's PLDA transform
depends on the OpenBLAS thread count).
Arrays are stored exactly as the release's numpy code holds them at the BLAS call sites:
mean1 (256,) f64, mean2 (128,) f32, lda (256,128) f32 C, plda_mu (128,) f64,
plda_tr_T (128,128) f64 C = np.ascontiguousarray(plda_tr.T) -- numpy's `_bad_strides` copy (plda_tr has a
negative stride) used by `(x0-mu).dot(plda_tr.T)`
plda_psi (128,) f64 = acvar[::-1]
source env/canonical.sh && python -s tools/export_assets.py
"""
import hashlib
import json
import os
import subprocess
import sys
from pathlib import Path
import numpy as np
PROJ = Path(__file__).resolve().parents[1]
BUNDLE = PROJ.parent / "anuvani_diar_v1" / "bundle"
OUT = PROJ / "assets"
def compute():
from scipy.linalg import eigh
x = np.load(BUNDLE / "hub/plda/xvec_transform.npz")
p = np.load(BUNDLE / "hub/plda/plda.npz")
plda_mu, plda_tr, plda_psi = p["mu"], p["tr"], p["psi"]
# verbatim from diarizen/clustering/VBx.py::vbx_setup
W = np.linalg.inv(plda_tr.T.dot(plda_tr))
B = np.linalg.inv((plda_tr.T / plda_psi).dot(plda_tr))
acvar, wccn = eigh(B, W)
plda_psi = acvar[::-1]
plda_tr = wccn.T[::-1]
return dict(mean1=x["mean1"], mean2=x["mean2"], lda=np.ascontiguousarray(x["lda"]), plda_mu=plda_mu,
plda_tr_T=np.ascontiguousarray(plda_tr.T), plda_psi=np.ascontiguousarray(plda_psi))
def digest(arrays):
return {k: hashlib.sha256(np.ascontiguousarray(v).tobytes()).hexdigest() for k, v in sorted(arrays.items())}
def main():
if len(sys.argv) > 1 and sys.argv[1] == "--digest":
print(json.dumps(digest(compute())))
return
if os.environ.get("OPENBLAS_NUM_THREADS") != "1":
raise SystemExit("source env/canonical.sh first")
arrays = compute()
ref = digest(arrays)
env64 = dict(os.environ, OPENBLAS_NUM_THREADS="64")
other = json.loads(subprocess.check_output([sys.executable, "-s", __file__, "--digest"], env=env64))
differs = sorted(k for k in ref if other[k] != ref[k])
OUT.mkdir(exist_ok=True)
manifest = dict(source={f: hashlib.sha256((BUNDLE / "hub/plda" / f).read_bytes()).hexdigest()
for f in ("xvec_transform.npz", "plda.npz")},
note=("computed with scipy 1.15.3 eigh in the canonical environment (OPENBLAS_NUM_THREADS=1, the "
"setting that reproduced the frozen RTTMs)"),
thread_dependence=dict(differs_at_64_threads=differs, digest_at_64_threads=other),
arrays={})
for k, v in arrays.items():
v = np.ascontiguousarray(v)
le = v.astype(v.dtype.newbyteorder("<"))
(OUT / f"{k}.bin").write_bytes(le.tobytes())
manifest["arrays"][k] = dict(dtype=str(v.dtype), shape=list(v.shape), sha256=ref[k])
(OUT / "MANIFEST.json").write_text(json.dumps(manifest, indent=1, sort_keys=True))
print(json.dumps(manifest["arrays"], indent=1))
if __name__ == "__main__":
main()
|