Optimizing AI for Mobile: The Power of Model Quantization (3)
Part 3: Quantizing and Evaluating Speech Models with Executorch
Optimizing AI for Mobile: The Power of Model Quantization (3)
Part 3: Quantizing and Evaluating Speech Models with Executorch

In the last part, we chose Post-Training Quantization (PTQ) with Executorch as the most suitable approach for deploying Hugging Face’s Wav2Vec2 model on mobile devices. Now, we’ll take a hands-on look at how the quantization process works, how calibration data is used, and how to evaluate the resulting model’s real-world performance.
Defining the Quantization Goals
Before diving into the implementation, it’s essential to set clear, measurable objectives for the quantized model:
Model size: 250 MB ≤ disk size ≤ 350 MB RAM usage: ≤ 250 MB peak inference RAM Accuracy: < 2× degradation in PER and FER on TIMIT core test set Speed: 1-second audio processed in ≤ 250 ms
These targets provide a balance between efficiency and fidelity - ensuring the model stays accurate enough while meeting mobile hardware limits.
The Project Overview
Below is the directory structure of the project (see the image in the article for reference). Each script has a specific purpose within the pipeline.
phoneme_quant_eval/
├─ scripts/
│ ├─ evaluate_origin_model.py # Evaluate original Hugging Face model
│ ├─ evaluate_quantized_model.py # Evaluate quantized Executorch model
│ ├─ quantize_model.py # Perform quantization and export .pte
│ ├─ ipa_feature_map.py # IPA phoneme → feature vector mapping
│ └─ timit_to_ipa_map.py # TIMIT phoneme → IPA mapping
├─ data/ # TIMIT dataset (TRAIN/TEST)
├─ exported/ # Output quantized models
└─ README.md # Setup and usage guide
The full codebase is available at ***here***.
The Dataset: TIMIT and Phoneme Mapping
We use the TIMIT dataset - a classic benchmark for phoneme recognition.
TIMIT provides .WAV audio files paired with .PHN phoneme annotations.
However, its phoneme symbols differ from the International Phonetic Alphabet (IPA).
That’s why we include two crucial mapping scripts:
**timit_to_ipa_map.py**: Converts TIMIT phonemes (likeaa,sh,jh) into IPA symbols (ɑ,ʃ,dʒ).**ipa_feature_map.py: Associates each IPA phoneme with a list of articulatory features, enabling Feature Error Rate (FER) computation alongside traditional Phoneme Error Rate (PER)**.
Stage 1: Quantizing the Model with Executorch
The quantization process consists of:
- Exporting the pretrained Wav2Vec2 model from Hugging Face.
- Preparing calibration data using a subset of TIMIT’s training audio.
- Applying static per-channel symmetric quantization through the Executorch XNNPACK backend.
- Exporting the final
.ptefile for on-device execution.
1. Loading and Preparing the Model
model = Wav2Vec2ForCTC.from_pretrained(MODEL_ID).eval()
feat = Wav2Vec2FeatureExtractor.from_pretrained(MODEL_ID)
target_sr = getattr(feat, "sampling_rate", 16000)
sample_len = target_sr * EXAMPLE_SECONDS
We load the KoelLabs/xlsr-english-01 model and define a short input duration (1 second) to generate representative calibration data.
2. Defining the Quantization Configuration
quantizer = XNNPACKQuantizer()
qconfig = get_symmetric_quantization_config(is_per_channel=True, is_dynamic=False)
quantizer.set_global(qconfig)
This sets the quantization to static symmetric per-channel - the same method discussed in Part 2. Specific modules like normalization and dropout layers are excluded to avoid numerical instability:
# Disable quantization for sensitive or non-learnable layers
quantizer.module_name_config["wav2vec2.feature_extractor.conv_layers.0"] = None
quantizer.module_name_config["wav2vec2.feature_extractor.conv_layers.1"] = None
quantizer.module_name_config["wav2vec2.feature_extractor.conv_layers.6"] = None
quantizer.module_name_config["wav2vec2.feature_projection.layer_norm"] = None
quantizer.module_name_config["wav2vec2.feature_projection.dropout"] = None
quantizer.module_name_config["wav2vec2.encoder.final_layer_norm"] = None
quantizer.module_name_config["lm_head"] = None
# Explicitly include only core layers for quantization
quantizer.module_name_config["wav2vec2.feature_projection.projection"] = qconfig
for i in range(0, 23):
quantizer.module_name_config[f"wav2vec2.encoder.layers.{i}"] = qconfig
3. Calibration
Calibration is essential for Post-Training Quantization. Here, 300 audio samples from the TIMIT TRAIN directory are used to estimate activation ranges:
for wav_path in iter_train_wavs(DATA_ROOT):
wav = load_and_resample(wav_path, target_sr)
inp = pad_or_trim(wav.unsqueeze(0), sample_len)
prepared(inp) # Run through model to record activation stats
This step ensures the quantizer determines realistic scaling factors for each layer’s weights and activations.
4. Conversion and Export
After calibration, the model is quantized and exported for Executorch runtime:
quantized_model = convert_pt2e(prepared)
exported_quant = torch.export.export(quantized_model, example_inputs, strict=True)
edge = to_edge_transform_and_lower(exported_quant, partitioner=[XnnpackPartitioner(allow_partial=True)])
exec_prog = edge.to_executorch(config=ExecutorchBackendConfig(extract_delegate_segments=True))
OUTPUT_PTE.write_bytes(exec_prog.buffer)
The final .pte file (≈ 300 MB) is optimized for XNNPACK, making it runnable directly on mobile CPUs.
Stage 2: Evaluating Model Performance
The Direction
To verify the quantized model meets our goals, we test:
- Accuracy: Phoneme Error Rate (PER) and Feature Error Rate (FER)
- Speed: Real-Time Factor (RTF)
- Memory: Peak RAM usage
- Size: Disk footprint
Evaluation Method
1. Phoneme Alignment and Conversion
TIMIT phonemes are converted to IPA using timit_to_ipa_map.py.
Predicted outputs from the model are decoded to IPA tokens through Wav2Vec2Processor.
The comparison uses ***Levenshtein distance***, implemented manually to compute both PER and FER.
2. Feature-Level Error Calculation
FER is computed by comparing phoneme features (place, manner, voicing, etc.) defined in ipa_feature_map.py.
This allows a finer-grained evaluation beyond just symbol mismatches.
3. Real-Time Factor and RAM Usage
Each model inference is wrapped in a memory monitor:
result, peak_mb = measure_peak_rss_during(_run)
RTF (real-time factor) is the ratio of processing time to audio duration. A lower RTF means faster performance - our target is ≤ 0.25× for 1-second clips.
4. Comparison Code
Both evaluation scripts - evaluate_origin_model.py and evaluate_quantized_model.py- share the same logic, except the latter loads the .pte model via Executorch runtime:
runtime = Runtime.get()
program = runtime.load_program(pte_file_path, verification=Verification.Minimal)
forward = program.load_method("forward")
Each test logs PER, FER, RTF, and peak RAM for every audio sample, and averages the results across the dataset.
The Results
The quantized model achieved:
- Model size: ~320 MB
- Peak RAM usage: ~240 MB
- Speed: ~0.22× real-time
- Accuracy: ~1.7× PER/FER degradation compared to the original
This meets all our predefined objectives. In practice, speech recognition remained intelligible and responsive even on mid-tier smartphones.
What’s Next?
This concludes the heavy lifting - our Wav2Vec2 model is now quantized, evaluated, and ready for deployment.
In the next article, we’ll move to the final stage: integrating the quantized .pte model into a React Native app using ***react-native-executorch*** and building a real-time mobile speech recognition interface.
메타데이터
- post_id
- 9829bd1fc502
- slug
- optimizing-ai-for-mobile-the-power-of-model-quantization-3-9829bd1fc502
- url
- https://medium.com/@cupid20103/optimizing-ai-for-mobile-the-power-of-model-quantization-3-9829bd1fc502
- canonical_url
- https://medium.com/@cupid20103/optimizing-ai-for-mobile-the-power-of-model-quantization-3-9829bd1fc502
- author_url
- https://medium.com/@cupid20103
- status
- ok
- fetched_at
- 2026-08-01 14:19:49