LoRA adapter: trained for Singapore English ASR alignment
The whole bundle runs natively on Apple Silicon via mlx. The LoRA is switchable — disable it for plain text generation, enable it for speech transcription. No merging required.
Results
Evaluated on a held-out Singapore English ASR test split (3000 clips):
Model
WER ↓
MERaLiON-2 (baseline, end-to-end speech-LM)
25.78%
This model (Gemma-4-E4B + MERaLiON speech tower + ours)
18.86%
Improvement
−6.92pp absolute (−26.8% relative)
WER is computed with jiwer's standard normalization: lowercase, punctuation removed, whitespace collapsed. <SpeakerN>: dialogue prefixes (occasionally emitted by the decoder) are stripped before scoring — this happens natively inside transcribe_with_pipeline, not as a post-hoc evaluation hack.
Provenance
This is a composite work. See PROVENANCE.md for the full chain-of-custody on every weight file.
The decoder is the only quantized component. The speech tower stays at fp16 (it came that way from the MERaLiON-3-10B-MLX-4bit repo, where only the decoder shards are 4-bit). Our trained additions are at fp32 for maximum fidelity at minimal cost (208 MB combined).
1pip install git+https://github.com/ajentik/elderwise-mlx.git
2# or clone and pip install -e .
Then:
python
1from elderwise.inference import load_pipeline, transcribe_with_pipeline
2from huggingface_hub import snapshot_download
34# Download the bundle5local = snapshot_download("majentik/Gemma-4-E4B-MERaLiON-Speech-LoRA-MNSC-MLX")67pipeline = load_pipeline(8 meralion_dir=f"{local}/speech_encoder",9 gemma_id=f"{local}/decoder",# local path, not HF id10 projector_path=f"{local}/projector",11 lora_path=f"{local}/lora",12 lora_rank=16,13 lora_target_names=(14"q_proj","k_proj","v_proj","o_proj",15"gate_proj","up_proj","down_proj",16),17)1819text = transcribe_with_pipeline(pipeline,"your_audio.wav")20print(text)
The pipeline:
Resamples audio to 16 kHz.
Runs the MERaLiON encoder → adaptor to get 3584-d speech features.
Projects them into Gemma's embedding space.
Prompts Gemma with "Transcribe the following audio: " + projected speech embeddings + the PLE bypass (see Alignment notes).
Greedy-decodes up to 128 tokens, strips any <SpeakerN>: prefix, returns the result.
Alignment notes
What is included
Projector: from scratch, 7 layers worth of params: LayerNorm(3584) → Linear(3584→3072) → SiLU → Linear(3072→2560) → RMSNorm(2560). The final RMSNorm matters — Gemma-4 normalizes its embeddings to ‖x‖ ≈ 1.0 and projector outputs need to live on that manifold.
LoRA adapter: switchable speech-alignment adapter. Speech mode enables the adapter; text mode disables it and falls back to base decoder behavior.
Data
The alignment adapter was trained on Singapore English ASR data. The public model card intentionally keeps internal optimization details high-level.
The non-obvious bit: Gemma-4's PLE
Gemma-4 uses Per-Layer Embeddings (PLE) as a side-channel into every transformer layer. When you call the model with inputs=None and only input_embeddings (as you must for speech, where there's no clean token id), mlx_lm runs nearest-neighbor recovery — it argmins your embeddings against the vocab table and uses the resulting fake token ids to populate PLE.
If your speech embeddings are off-manifold (they will be without the final RMSNorm), the nearest-neighbor returns garbage rare tokens, and Gemma reverts to its strongest priors (PII redaction templates, in our case — we measured 592% WER before fixing this).
The fix lives in elderwise/per_layer.py: we bypass nearest-neighbor by building per_layer_inputs explicitly — zeros for speech positions (silences the PLE side-channel where it has no signal anyway), and real embed_tokens_per_layer(token_ids) * scale for prompt/transcript positions.
This + three other compounding fixes (projector RMSNorm, prompt tokenization match, LoRA scale restore) is what made the model actually condition on speech. See the elderwise repo for the full diagnostic trail.
Limitations
Singapore English only. Not evaluated on other accents/locales.
Substitution errors on rare proper nouns. The model phonetically captures unusual names but substitutes (e.g., "Tendon" → "Tender", "Vindaloo" survives but "Fallon" → "Felten"). This is a data scale problem, not a capacity problem.
8-bit decoder may show occasional artifacts vs. the full bf16 base.
No streaming. Audio is encoded in one shot, then transcribed.
No timestamps, no diarization. Plain transcription only. (The <SpeakerN>: artifacts are stripped, not used.)
Citation
If you use this model, please cite:
bibtex
1@misc{gemma4_meralion_speech_lora_mnsc_mlx,
2 title = {Gemma-4-E4B-MERaLiON Speech LoRA for Singapore English},
3 author = {majentik},
4 year = {2026},
5 url = {https://huggingface.co/majentik/Gemma-4-E4B-MERaLiON-Speech-LoRA-MNSC-MLX}
6}