File size: 2,635 Bytes
beff3ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""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()