Card: base_model_relation: quantized (so the conversion lists under the base model's Quantizations, not Finetunes)
7ed9efe verified |
Download README.md from mlboydaisuke/Whisper-base-ExecuTorch: direct link, hf CLI and curl.
- Browser
- Download file 5.49 kB
-
https://huggingface.co/mlboydaisuke/Whisper-base-ExecuTorch/resolve/main/README.md
- Command line
-
hf download hf://mlboydaisuke/Whisper-base-ExecuTorch/README.md
-
curl -L -o README.md https://huggingface.co/mlboydaisuke/Whisper-base-ExecuTorch/resolve/main/README.md
5.49 kB
| license: apache-2.0 | |
| tags: | |
| - executorch | |
| - xnnpack | |
| - pte | |
| - on-device | |
| - automatic-speech-recognition | |
| base_model: | |
| - openai/whisper-base | |
| base_model_relation: quantized | |
| # Whisper-base β ExecuTorch (encoder + decoder) | |
| 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. | |
| - **Source**: [openai/whisper-base](https://huggingface.co/openai/whisper-base) | |
| - **License**: Apache-2.0 | |
| - **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. | |
| ## Verification (Mac arm64, executorch 1.4.0, torch 2.13.0) | |
| 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 | |
| ```bash | |
| 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. | |
| (conversion scripts: [executorch-models](https://github.com/john-rocky/executorch-models)) | |