Nawah-ASR-50M-Demo / load_v5.py
oddadmix's picture
Serve Nawah-ASR-118M-v5 (own 50M encoder + GRPO); 50 Hz audio tokens
beff3ce verified
Raw
History Blame Contribute Delete
2.64 kB
"""Load Nawah-ASR-118M-v5. Use load_v5() -- a bare from_pretrained runs at the wrong frame rate.
The audio tower's SHAPE reconstructs correctly from config.json (d_model 576, 12 layers). What
does not survive is the frame rate: Qwen2Audio's encoder ends in a stride-2 average pool that
v5 removes, and that pooler has no parameters and is rebuilt on every load. So from_pretrained
returns all 300 tensors byte-identical, no warning, at 25 Hz instead of the 50 Hz it was
trained at. config.json records audio_config.pool_stride: 1; stock transformers ignores it.
from load_model import load_v5, transcribe
model, tok, fe = load_v5("oddadmix/Nawah-ASR-118M-v5")
print(transcribe(model, tok, fe, wav)) # float32 mono @ 16 kHz
"""
import torch
from transformers import (AutoTokenizer, Qwen2AudioForConditionalGeneration,
WhisperFeatureExtractor)
from build_model import set_audio_frame_rate
def load_v5(repo: str, token: str | None = None, dtype=torch.float32):
model = Qwen2AudioForConditionalGeneration.from_pretrained(repo, dtype=dtype, token=token)
set_audio_frame_rate(model, 1) # 1 = 50 Hz
model.eval()
_, n = model.model.audio_tower._get_feat_extract_output_lengths(torch.tensor([3000]))
assert int(n) == 1500, f"expected 50 Hz (1500 tokens / 30 s), got {int(n)}"
tok = AutoTokenizer.from_pretrained(repo, token=token)
fe = WhisperFeatureExtractor.from_pretrained(repo, token=token)
return model, tok, fe
@torch.no_grad()
def transcribe(model, tok, fe, wav, sr=16000, max_new_tokens=160):
feats = fe([wav], sampling_rate=sr, return_attention_mask=True, return_tensors="pt")
_, n = model.model.audio_tower._get_feat_extract_output_lengths(feats.attention_mask.sum(-1))
cv = tok.convert_tokens_to_ids
# The placeholder is "<audio>" (id 32000). "<|AUDIO|>" is NOT in this tokenizer -- it maps
# to UNK (id 3), which silently fills the prefix with unknown tokens and transcribes noise.
aud, a0, a1 = cv("<audio>"), cv("<|audio_start|>"), cv("<|audio_end|>")
assert tok.unk_token_id not in (aud, a0, a1), "audio special tokens missing from tokenizer"
prefix = [tok.bos_token_id, a0] + [aud] * int(n[0]) + [a1]
ids = torch.tensor([prefix])
out = model.generate(input_ids=ids, attention_mask=torch.ones_like(ids),
input_features=feats.input_features,
feature_attention_mask=feats.attention_mask,
max_new_tokens=max_new_tokens, do_sample=False)
return tok.decode(out[0, ids.shape[1]:], skip_special_tokens=True).strip()