Speech recognition in two
.pte
files: the encoder runs once per 30-second window, the
decoder once per generated token. Putting them in one graph would re-encode the audio on
every step.
graph
build
file
size (MB)
corr vs fp32 eager
ms
eager ms
encoder
XNNPACK fp32
whisper_base_encoder_xnnpack_fp32.pte
82.4
1.000000
120.1
43.8
encoder
XNNPACK fp16
whisper_base_encoder_xnnpack_fp16.pte
43.1
0.999997
217.6
43.0
encoder
XNNPACK int8
whisper_base_encoder_xnnpack_int8.pte
25.9
0.999374
114.3
45.7
encoder
Core ML
whisper_base_encoder_coreml_all.pte
41.4
0.999983
28.1
44.2
decoder
XNNPACK fp32
whisper_base_decoder_xnnpack_fp32.pte
314.4
1.000000
36.1
20.0
decoder
XNNPACK fp16
whisper_base_decoder_xnnpack_fp16.pte
157.4
0.999981
77.3
19.4
decoder
Core ML
whisper_base_decoder_coreml_all.pte
104.2
0.999861
5.0
20.0
Every file takes and returns fp32 tensors (token ids stay int64), so any encoder pairs with
any decoder. The lightest working pair is 130.1 MB.
Encoder input
: log-mel spectrogram
[1, 80, 3000]
— 30 s at 16 kHz, 80 mel bins, hop
160, window 400, exactly what
WhisperFeatureExtractor
produces
Decoder input
: the encoder output plus
decoder_input_ids [1, 128]
int64,
left-aligned and padded. Start with
<|startoftranscript|>
, a language token,
<|transcribe|>
,
<|notimestamps|>
.
Decoding
No KV cache: the decoder is a static graph over a fixed 128-token window, so a greedy step
is take
argmax
of row
len-1
, append it, run again. Stop at
<|endoftext|>
(50257). 128
tokens covers a 30-second window of ordinary speech; past that, start a new window.
That costs a full 128-position forward pass per token, which is the price of a static graph
that runs unchanged across runtimes and precisions.
The two wrappers compose back to
WhisperForConditionalGeneration
exactly — max_abs_diff
0.000e+00
— and every graph matches torch fp32 eager at the correlations above. Timings
are medians over 5 runs in one process: a relative reference, not a device number.
Two things worth knowing about the sizes
The decoder
.pte
is larger than the decoder's weights.
Whisper ties
proj_out.weight
to
decoder.embed_tokens.weight
, but the two uses need different representations: an
embedding table the portable kernels index into, and the same values packed into the XNNPACK
delegate's blob for the output matmul. Tying them in PyTorch does not tie them here.
Referencing the weight through
F.linear
instead of the
proj_out
module does not either —
exported both ways, whisper-tiny's decoder comes out at 198.0 MB exactly.
The decoder's int8 build is not shipped.
Dynamic int8 quantizes the linear weights and
leaves the token embedding table in fp32, and that table is 106.2 MB — 51,865 tokens at
512 dimensions. On this size that table is most of the file, so int8 lands at 159.6 MB against fp16's 157.4 MB — larger, because fp16 halves the table too. It converts and holds, but nothing would pick it, so it is not shipped.
Until recently there was no decoder int8 build at all, and this card said PT2E was observing
the int64
decoder_input_ids
. That was wrong on both halves.
XNNPACKQuantizer.transform_for_annotation
rewrites every scalar argument of
add.Tensor
/
mul.Tensor
as
torch.tensor(float(arg))
whatever the node's dtype — one line
in ExecuTorch's
backends/xnnpack/quantizer/xnnpack_quantizer_utils.py
, still present on
main. In this decoder the casualty is
position_ids = torch.arange(...) + past_key_values_length
(
modeling_whisper.py:749
,
past_key_values_length
being a python
int
): it comes back
float32, and the failure lands on
self.weight[position_ids]
— the
position
embedding
lookup, not the token ids, and no observer involved. Measured by running
prepare_pt2e
with an empty quantizer and printing the failing node.
Checked in the task's own units
Correlation is a first filter. These are the numbers that decide:
encoder int8
— measured end to end — word error rate against the fp32 encoder: mean WER 0.0% (worst clip 0.0%) over 5 spoken sentences, int8 encoder against the fp32 encoder with the same fp32 decoder and the same waveform; the fp32 arm transcribes all five correctly, so the comparison is against a working control rather than against noise.
The sensitivity of that test, measured by injecting random noise into whisper-tiny's encoder output: rel_l2 0.03 (what int8 actually costs) and 0.10 both give WER 0.000; 0.20 and 0.40 give 0.025. Five clean sentences leave headroom, so a pass means
does not break the transcript
, not
indistinguishable at any error level
.
Conversion
python convert/export_whisper.py base
The ExecuTorch tree ships a single-graph Whisper example under
examples/models/whisper
;
this is that model with the halves separated.
Whisper-base-ExecuTorch huggingface.co is an AI model on huggingface.co that provides Whisper-base-ExecuTorch's model effect (), which can be used instantly with this mlboydaisuke Whisper-base-ExecuTorch model. huggingface.co supports a free trial of the Whisper-base-ExecuTorch model, and also provides paid use of the Whisper-base-ExecuTorch. Support call Whisper-base-ExecuTorch model through api, including Node.js, Python, http.
Whisper-base-ExecuTorch huggingface.co is an online trial and call api platform, which integrates Whisper-base-ExecuTorch's modeling effects, including api services, and provides a free online trial of Whisper-base-ExecuTorch, you can try Whisper-base-ExecuTorch online for free by clicking the link below.
mlboydaisuke Whisper-base-ExecuTorch online free url in huggingface.co:
Whisper-base-ExecuTorch is an open source model from GitHub that offers a free installation service, and any user can find Whisper-base-ExecuTorch on GitHub to install. At the same time, huggingface.co provides the effect of Whisper-base-ExecuTorch install, users can directly use Whisper-base-ExecuTorch installed effect in huggingface.co for debugging and trial. It also supports api for free installation.
Whisper-base-ExecuTorch install url in huggingface.co: