Skip to content

Commit c305335

Browse files
authored
Test Whisper on Ascend NPU using ACL Python API (#2986)
1 parent b1db3ea commit c305335

3 files changed

Lines changed: 266 additions & 4 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../rknn/export_onnx.py
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2025 Xiaomi Corporation
3+
4+
import argparse
5+
import base64
6+
from typing import List
7+
8+
import kaldi_native_fbank as knf
9+
import librosa
10+
import numpy as np
11+
from ais_bench.infer.interface import InferSession
12+
13+
14+
def get_args():
15+
parser = argparse.ArgumentParser()
16+
parser.add_argument(
17+
"--encoder",
18+
type=str,
19+
required=True,
20+
help="Path to the encoder",
21+
)
22+
23+
parser.add_argument(
24+
"--decoder",
25+
type=str,
26+
required=True,
27+
help="Path to the decoder",
28+
)
29+
30+
parser.add_argument(
31+
"--tokens",
32+
type=str,
33+
required=True,
34+
help="Path to the tokens",
35+
)
36+
37+
parser.add_argument(
38+
"--wav",
39+
type=str,
40+
required=True,
41+
help="Path to the test wav",
42+
)
43+
44+
return parser.parse_args()
45+
46+
47+
def causal_mask_1d(n: int, L: int):
48+
"""
49+
Returns a 1-D int mask of shape (L,) with:
50+
0 -> allowed
51+
1 -> masked (will be converted to -inf later)
52+
"""
53+
mask = np.ones((L,), dtype=np.int32)
54+
if n > 0:
55+
mask[:n] = 0
56+
return mask
57+
58+
59+
def load_audio(filename: str) -> np.ndarray:
60+
samples, _ = librosa.load(filename, sr=16000)
61+
62+
samples = np.ascontiguousarray(samples)
63+
return samples
64+
65+
66+
def compute_features(samples: np.ndarray, dim: int = 80) -> np.ndarray:
67+
"""
68+
Returns:
69+
Return a 1-D float32 tensor of shape (1, 80, 3000) containing the features.
70+
"""
71+
features = []
72+
opts = knf.WhisperFeatureOptions()
73+
opts.dim = dim
74+
online_whisper_fbank = knf.OnlineWhisperFbank(opts)
75+
online_whisper_fbank.accept_waveform(16000, samples)
76+
online_whisper_fbank.input_finished()
77+
78+
features = np.stack(
79+
[
80+
online_whisper_fbank.get_frame(i)
81+
for i in range(online_whisper_fbank.num_frames_ready)
82+
]
83+
)
84+
log_spec = np.log10(np.clip(features, a_min=1e-10, a_max=None))
85+
log_spec = np.maximum(log_spec, log_spec.max() - 8.0)
86+
mel = (log_spec + 4.0) / 4.0
87+
num_frames = mel.shape[0]
88+
target = 3000
89+
if num_frames < target:
90+
mel = np.pad(
91+
mel,
92+
pad_width=((0, target - num_frames), (0, 0)),
93+
mode="constant",
94+
constant_values=0,
95+
)
96+
97+
mel = np.expand_dims(mel.T, axis=0)
98+
mel = np.ascontiguousarray(mel)
99+
100+
return mel
101+
102+
103+
def load_tokens(filename):
104+
tokens = dict()
105+
with open(filename, "r") as f:
106+
for line in f:
107+
t, i = line.split()
108+
tokens[int(i)] = t
109+
return tokens
110+
111+
112+
class OmModel:
113+
def __init__(self, encoder: str, decoder: str):
114+
self.encoder = InferSession(device_id=0, model_path=encoder, debug=False)
115+
self.decoder = InferSession(device_id=0, model_path=decoder, debug=False)
116+
117+
name = self.encoder.get_inputs()[0].name
118+
119+
if ".en" in name:
120+
self.sot_sequence = [50257, 50362]
121+
self.eot = 50256
122+
else:
123+
self.sot_sequence = [50258, 50259, 50359, 50363]
124+
self.eot = 50257
125+
126+
if "tiny" in name:
127+
self.n_text_layer = 4
128+
self.n_text_ctx = 448
129+
self.n_text_state = 384
130+
elif "base" in name:
131+
self.n_text_layer = 6
132+
self.n_text_ctx = 448
133+
self.n_text_state = 512
134+
elif "small" in name:
135+
self.n_text_layer = 12
136+
self.n_text_ctx = 448
137+
self.n_text_state = 768
138+
elif "medium" in name:
139+
self.n_text_layer = 24
140+
self.n_text_ctx = 448
141+
self.n_text_state = 1024
142+
else:
143+
assert False, f"Unsupported encoder input {name}"
144+
145+
print("---encoder---")
146+
for i in self.encoder.get_inputs():
147+
print(i.name, i.datatype, i.shape)
148+
149+
print("-----")
150+
151+
for i in self.encoder.get_outputs():
152+
print(i.name, i.datatype, i.shape)
153+
154+
print("---decoder---")
155+
for i in self.decoder.get_inputs():
156+
print(i.name, i.datatype, i.shape)
157+
158+
print("-----")
159+
160+
for i in self.decoder.get_outputs():
161+
print(i.name, i.datatype, i.shape)
162+
163+
def get_self_cache(self) -> List[np.ndarray]:
164+
self_cache = []
165+
batch_size = 1
166+
for i in range(self.n_text_layer):
167+
k = np.zeros(
168+
(batch_size, self.n_text_ctx, self.n_text_state), dtype=np.float32
169+
)
170+
v = np.zeros(
171+
(batch_size, self.n_text_ctx, self.n_text_state), dtype=np.float32
172+
)
173+
self_cache.extend([k, v])
174+
return self_cache
175+
176+
def run_encoder(self, x: np.ndarray):
177+
"""
178+
Args:
179+
x: (1, 80, 3000), np.float32
180+
Returns:
181+
cross_kv:
182+
- (k, v) for layer 0
183+
- (k, v) for layer 1
184+
- (k, v) for layer 2
185+
- (k, v) for layer 3
186+
"""
187+
out = self.encoder.infer([x])
188+
return out
189+
190+
def run_decoder(self, tokens: np.ndarray, self_kv, cross_kv, offset, mask):
191+
"""
192+
Args:
193+
tokens: (1, 1), np.int32
194+
offset: (1,), np.int32
195+
mask: (model.n_text_ctx,), np.int32
196+
Returns:
197+
logit: (1, 1, vocab_size)
198+
this_self_kv
199+
"""
200+
return self.decoder.infer([tokens] + self_kv + cross_kv + [offset, mask])
201+
202+
203+
def main():
204+
args = get_args()
205+
print(vars(args))
206+
samples = load_audio(args.wav)
207+
features = compute_features(samples)
208+
print("features", features.shape)
209+
210+
model = OmModel(args.encoder, args.decoder)
211+
212+
cross_kv = model.run_encoder(features)
213+
214+
self_kv = model.get_self_cache()
215+
216+
offset = np.array([0], dtype=np.int32)
217+
for t in model.sot_sequence:
218+
token = np.array([[t]], dtype=np.int32) # sot
219+
mask = causal_mask_1d(offset.item(), model.n_text_ctx)
220+
221+
out = model.run_decoder(
222+
tokens=token, self_kv=self_kv, cross_kv=cross_kv, offset=offset, mask=mask
223+
)
224+
225+
for i in range(1, len(out)):
226+
self_kv[i - 1][:, offset.item() : offset.item() + 1, :] = out[i]
227+
228+
offset += 1
229+
230+
idx = out[0][0, 0].argmax()
231+
232+
eot = model.eot
233+
234+
ans = []
235+
236+
while idx != eot and offset.item() < 100:
237+
ans.append(idx)
238+
token = np.array([[idx]], dtype=np.int32)
239+
240+
mask = causal_mask_1d(offset.item(), model.n_text_ctx)
241+
242+
out = model.run_decoder(
243+
tokens=token, self_kv=self_kv, cross_kv=cross_kv, offset=offset, mask=mask
244+
)
245+
246+
for i in range(1, len(out)):
247+
self_kv[i - 1][:, offset.item() : offset.item() + 1, :] = out[i]
248+
249+
offset += 1
250+
idx = out[0][0, 0].argmax()
251+
252+
print(ans)
253+
id2token = load_tokens(args.tokens)
254+
255+
s = b""
256+
for i in ans:
257+
if i in id2token:
258+
s += base64.b64decode(id2token[i])
259+
260+
print(s.decode().strip())
261+
return
262+
263+
264+
if __name__ == "__main__":
265+
main()

scripts/whisper/rknn/test_on_rk3588_board.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@ def run_encoder(self, x: np.ndarray):
201201
- (k, v) for layer 3
202202
"""
203203
out = self.encoder.inference(inputs=[x.numpy()])
204-
print("after running encoder", len(out))
205204
return out
206205

207206
def get_self_cache(self) -> List[np.ndarray]:
@@ -239,11 +238,9 @@ def main():
239238
id2token = load_tokens(args.tokens)
240239

241240
if ".en" in args.encoder:
242-
print("here", args.encoder)
243241
sot_sequence = [50257, 50362]
244242
eot = 50256
245243
else:
246-
print("not here", args.encoder)
247244
sot_sequence = [50258, 50259, 50359, 50363]
248245
eot = 50257
249246

@@ -296,7 +293,6 @@ def test(model, id2token):
296293
for t in model.sot_sequence:
297294
token = np.array([[t]], dtype=np.int32) # sot
298295
mask = causal_mask_1d(offset.item(), model.n_text_ctx)
299-
print(t, model.sot_sequence, token, mask.shape, len(cross_kv), len(self_kv))
300296

301297
out = model.run_decoder(
302298
tokens=token, self_kv=self_kv, cross_kv=cross_kv, offset=offset, mask=mask

0 commit comments

Comments
 (0)