Views
No views yet
LICENSE.decoder_init / decoder_prefill / decoder_step
graphs with an external KV-cache weight file.embed_tokens.bin table.hann_400.f32, mel_filters.f32).| File | Purpose |
|---|---|
encoder.fp16.onnx / encoder.int4.onnx(+.data) / encoder.fp32.onnx | Audio encoder (mel → audio features): fp16, REAL int4 (MatMulNBits), and FP32 variants. GPU default fp16; CUDA can use int4 for multi-stream; CPU uses fp32 |
decoder_init.int4.onnx | Decoder graph for the first step (no past KV) |
decoder_prefill.int4.onnx(+.data) | Prefill graph for the prompt span |
decoder_prefill.lastlog.int4.onnx | Prefill variant emitting only the last-position logits |
decoder_step.int4.onnx / decoder_step.int4.am.onnx | Single autoregressive step (.am = argmax fused) |
decoder_weights.int4.data | Shared int4 decoder weights (external data) |
decoder_bucket_prefill_0p6b_B1024.int4.onnx(+.data) / decoder_bucket_step_0p6b_B1024.int4.onnx(+.data) | CUDA-graph fixed-bucket (1024) decode graphs — fast single-stream CUDA path (optional) |
embed_tokens.bin | Token embedding table (fp16, raw little-endian) |
tokenizer.json, vocab.json, merges.txt, added_tokens.json | Byte-level BPE tokenizer |
config.json, preprocessor_config.json, tokenizer_config.json | Model / preprocessor config |
hann_400.f32, mel_filters.f32 | Precomputed Hann window & mel filterbank for the front-end |
Encoder naming (corrected 2026-06-03):encoder.int4.onnxis now the real int4 encoder (MatMulNBits) andencoder.fp32.onnxis FP32. Earlier revisions mislabeled the FP32 encoder asencoder.int4.onnxand shipped the real int4 asencoder.q4.onnx(now removed). If you cached an older revision, re-download.
pip install onnxruntime numpy soundfile tokenizers).
This same code works for both the 0.6B and 1.7B repos (hidden size is read from config.json):1import json, numpy as np, onnxruntime as ort, soundfile as sf
2from huggingface_hub import snapshot_download
3from tokenizers import Tokenizer
4
5MODEL_DIR = snapshot_download("jinhwan000/qwen3-asr-0.6b-onnx")
6WAV = "audio.wav" # 16 kHz mono
7
8# Qwen3-ASR chat scaffold (special token ids)
9IM_START, IM_END, EOT = 151644, 151645, 151643
10AUDIO_START, AUDIO_END, AUDIO_PAD, NL = 151669, 151670, 151676, 198
11SYSTEM_LINE, USER_LINE, ASSISTANT_LINE = [8948, NL], [872, NL], [77091, NL]
12EOS = {{EOT, IM_END}}
13HIDDEN = json.load(open(f"{{MODEL_DIR}}/config.json"))["decoder"]["hidden_size"]
14
15def log_mel(audio): # whisper-compatible log-mel using the shipped DSP constants
16 NFFT, HOP, NMELS, NBINS = 400, 160, 128, 201
17 hann = np.fromfile(f"{{MODEL_DIR}}/hann_400.f32", np.float32)
18 melf = np.fromfile(f"{{MODEL_DIR}}/mel_filters.f32", np.float32).reshape(NMELS, NBINS)
19 x = np.pad(audio, NFFT // 2, mode="reflect")
20 nfr = 1 + (len(x) - NFFT) // HOP
21 frames = np.stack([x[i*HOP:i*HOP+NFFT] * hann for i in range(nfr)])
22 spec = np.fft.rfft(frames, n=NFFT, axis=1)
23 mel = np.log10(np.maximum((spec.real**2 + spec.imag**2) @ melf.T, 1e-10))
24 mel = np.maximum(mel, mel.max() - 8.0)
25 mel = (mel + 4.0) / 4.0
26 return mel[:-1].T.astype(np.float32)[None] # [1,128,T] (drop last frame)
27
28audio, sr = sf.read(WAV, dtype="float32")
29if audio.ndim > 1: audio = audio.mean(1)
30assert sr == 16000, "expects 16 kHz mono"
31
32prov = ["CPUExecutionProvider"] # GPU: ["CUDAExecutionProvider"] (onnxruntime-gpu) or ["DmlExecutionProvider"]
33enc = ort.InferenceSession(f"{{MODEL_DIR}}/encoder.fp16.onnx", providers=prov)
34init = ort.InferenceSession(f"{{MODEL_DIR}}/decoder_init.int4.onnx", providers=prov)
35step = ort.InferenceSession(f"{{MODEL_DIR}}/decoder_step.int4.onnx", providers=prov)
36embed = np.fromfile(f"{{MODEL_DIR}}/embed_tokens.bin", np.float16).reshape(-1, HIDDEN)
37tok = Tokenizer.from_file(f"{{MODEL_DIR}}/tokenizer.json")
38
39# encode audio -> features, then build the prompt with N audio-pad slots
40af = enc.run(["audio_features"], {{"mel": log_mel(audio)}})[0] # [1, N, hidden]
41N = af.shape[1]
42prompt = ([IM_START] + SYSTEM_LINE + [IM_END, NL, IM_START] + USER_LINE +
43 [AUDIO_START] + [AUDIO_PAD]*N + [AUDIO_END, IM_END, NL, IM_START] + ASSISTANT_LINE)
44P = len(prompt)
45
46# prefill
47logits, pk, pv = init.run(None, {{
48 "input_ids": np.array([prompt], np.int64),
49 "position_ids": np.arange(P, dtype=np.int64)[None],
50 "audio_features": af,
51 "audio_offset": np.array([prompt.index(AUDIO_PAD)], np.int64)}})
52nxt, out, pos = int(logits[0, -1].argmax()), [], P
53budget = max(16, int(len(audio) / 16000 * 13))
54
55# greedy autoregressive decode with KV cache
56while nxt not in EOS and len(out) < budget:
57 out.append(nxt)
58 logits, pk, pv = step.run(None, {{
59 "input_embeds": embed[nxt].astype(np.float32)[None, None],
60 "position_ids": np.array([[pos]], np.int64),
61 "past_keys": pk, "past_values": pv}})
62 nxt = int(logits[0, -1].argmax()); pos += 1
63
64text = tok.decode([t for t in out if t not in EOS])
65text = text.split("<asr_text>")[-1].strip() # strip the "language <lang><asr_text>" prefix
66print(text)encoder.int4.onnx (smallest, slight accuracy drop) or encoder.fp32.onnx can be swapped for the encoder.decoder_prefill* (system-prompt KV reuse / streaming)
and decoder_bucket_* (CUDA-graph fast path) graphs for advanced inference loops.