"""Standalone CTC-submodel transcription on the dosing audio. Loads Canary, accesses model.timestamps_asr_model directly, calls its own transcribe() — bypassing the AED decoder entirely. Goal: see whether the CTC submodel renders the spoken digit sequence "eins null eins" as separate tokens or also collapses it. """ import json import subprocess import tempfile import torch from nemo.collections.asr.models import EncDecMultiTaskModel WAV_SRC = "/sweep/case_c414cf52_15-01-39.m4a" OUT_PATH = "/sweep/ctc_standalone_dosing.json" def transcode(src: str) -> str: dst = tempfile.NamedTemporaryFile(suffix=".wav", delete=False) dst.close() subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-i", src, "-ac", "1", "-ar", "16000", "-f", "wav", dst.name], check=True) return dst.name def to_jsonable(o): if hasattr(o, "tolist"): return o.tolist() if hasattr(o, "item"): return o.item() return str(o) def main(): print("loading Canary 1B v2 (just for the CTC submodel)", flush=True) model = EncDecMultiTaskModel.from_pretrained("nvidia/canary-1b-v2") model = model.to("cuda").to(torch.bfloat16) model.eval() tdt = model.timestamps_asr_model print(f"submodel: {type(tdt).__name__}", flush=True) wav = transcode(WAV_SRC) print(f"running tdt.transcribe(timestamps=True) on {WAV_SRC}", flush=True) with torch.amp.autocast("cuda", enabled=True, dtype=torch.bfloat16): with torch.no_grad(): hyps = tdt.transcribe([wav], timestamps=True) h = hyps[0] out = {"text": getattr(h, "text", None), "timestamp": getattr(h, "timestamp", None)} with open(OUT_PATH, "w") as f: json.dump(out, f, default=to_jsonable, ensure_ascii=False, indent=2) print(f"\nstandalone CTC text:\n{h.text}\n", flush=True) ts = h.timestamp if isinstance(h.timestamp, dict) else {} for k, v in ts.items(): n = len(v) if hasattr(v, "__len__") else "?" print(f"timestamp[{k}]: {n} entries", flush=True) print(f"\nfull dump -> {OUT_PATH}", flush=True) if __name__ == "__main__": main()