Views
No views yet
LICENSE.hann_400.f32, mel_filters.f32).<timestamp> slot's argmax
× 80 ms gives the word boundary time.| File | Purpose |
|---|---|
encoder.int4.onnx(+.data) | Audio encoder (mel → audio features), int4 |
decoder_init.int4.onnx(+.data) | Decoder forward pass producing <timestamp> logits, int4 |
vocab.json, merges.txt | Byte-level BPE tokenizer (vocab + merge ranks) |
hann_400.f32, mel_filters.f32 | Precomputed Hann window & mel filterbank for the front-end |
pip install onnxruntime numpy soundfile tokenizers:1import numpy as np, onnxruntime as ort, soundfile as sf
2from huggingface_hub import snapshot_download
3from tokenizers import Tokenizer, models, pre_tokenizers
4
5MODEL_DIR = snapshot_download("jinhwan000/qwen3-aligner-0.6b-onnx")
6WAV = "audio.wav" # 16 kHz mono
7ASR_TEXT = "the transcription to align" # from your ASR model
8
9AUDIO_START, AUDIO_END, AUDIO_PAD, TSID, SEG = 151669, 151670, 151676, 151705, 0.08 # 80 ms / bucket
10
11def log_mel(audio): # whisper-compatible log-mel using the shipped DSP constants
12 NFFT, HOP, NMELS, NBINS = 400, 160, 128, 201
13 hann = np.fromfile(f"{MODEL_DIR}/hann_400.f32", np.float32)
14 melf = np.fromfile(f"{MODEL_DIR}/mel_filters.f32", np.float32).reshape(NMELS, NBINS)
15 x = np.pad(audio, NFFT // 2, mode="reflect")
16 nfr = 1 + (len(x) - NFFT) // HOP
17 frames = np.stack([x[i*HOP:i*HOP+NFFT] * hann for i in range(nfr)])
18 spec = np.fft.rfft(frames, n=NFFT, axis=1)
19 mel = np.log10(np.maximum((spec.real**2 + spec.imag**2) @ melf.T, 1e-10))
20 mel = np.maximum(mel, mel.max() - 8.0)
21 return ((mel + 4.0) / 4.0)[:-1].T.astype(np.float32)[None] # [1,128,T] (drop last frame)
22
23# byte-level BPE from vocab.json + merges.txt (this repo has no tokenizer.json)
24tk = Tokenizer(models.BPE.from_file(f"{MODEL_DIR}/vocab.json", f"{MODEL_DIR}/merges.txt"))
25tk.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False, use_regex=True)
26
27audio, sr = sf.read(WAV, dtype="float32")
28if audio.ndim > 1: audio = audio.mean(1)
29assert sr == 16000, "expects 16 kHz mono"
30words = ASR_TEXT.split()
31
32enc = ort.InferenceSession(f"{MODEL_DIR}/encoder.int4.onnx", providers=["CPUExecutionProvider"])
33dec = ort.InferenceSession(f"{MODEL_DIR}/decoder_init.int4.onnx", providers=["CPUExecutionProvider"])
34
35af = enc.run(["audio_features"], {"mel": log_mel(audio)})[0] # [1, N, 1024]
36N = af.shape[1]
37
38# input = audio pads + per-word (BPE tokens + two <timestamp> slots: start, end)
39ids = [AUDIO_START] + [AUDIO_PAD]*N + [AUDIO_END]
40for w in words:
41 ids += tk.encode(w).ids + [TSID, TSID]
42S = len(ids)
43
44logits = dec.run(["logits"], {
45 "input_ids": np.array([ids], np.int64),
46 "position_ids": np.arange(S, dtype=np.int64)[None],
47 "audio_features": af,
48 "audio_offset": np.array([1], np.int64)})[0] # first audio-pad slot index
49
50arg = logits[0].argmax(-1) # logits last dim = 5000 timestamp buckets
51buckets = [int(arg[i]) for i in range(S) if ids[i] == TSID] # 2 per word
52for k, w in enumerate(words):
53 print(f"{buckets[2*k]*SEG:6.2f} - {buckets[2*k+1]*SEG:6.2f} {w}")<timestamp> slot's argmax × 80 ms gives the boundary time. Validation against the reference
aligner: timestamp buckets matched 62/62 (fp32) / 61/62 (int4, max 1 bucket = 80 ms); BPE
tokenization matched 239/239 input ids.