MaprangSTT
MaprangSTT is a Thai speech-to-Text model built on Cactus-Compute/whistle for Standard Thai and Regional Thai Dialects (Central, Isan / Northeastern, Northern / Khummuang, Korat, Southern / Pattani, and Elderly speech). It was trained on open-data datasets.
Trained on 1,022.78 hours (594,231 clips) of curated Thai speech using quantization-aware LoRA (CQ W4 STE + A8) and an expanded Thai SentencePiece vocabulary, the final model compiles into a standalone 32.49 MB .cact archive ready for fast on-device CPU/GPU/mobile inference.
- GitHub: https://github.com/wannaphong/MaprangSTT
- In-Browser Web Demo (
cactus-needle): https://wannaphong.github.io/MaprangSTT/demo
Model Summary
| Property | Value |
|---|---|
| Base Architecture | Cactus Whistle (8 encoder/decoder layers) |
| Fine-Tuning Method | Quantization-Aware LoRA (rank=16, alpha=32, 15 weight groups) + Trainable New Embedding Rows |
| Precision / Quantization | W4A8 (4-bit weights with straight-through estimator, 8-bit activations & KV cache) |
| Supported Languages | th (Thai), plus base en, de, fr, es, it, nl, pl |
| Vocabulary Expansion | +2,067 Thai tokens (+10,958 SentencePiece pieces + <|th|> language tag) |
Standalone .cact Size |
32.49 MB (maprang.cact, 681 tensors) |
| LoRA Adapter Size | 29.0 MB (adapter.safetensors) |
Dataset Summary
| Dataset Source | Split / Text Column | Valid Clips | Duration (Hours) |
|---|---|---|---|
Mozilla Common Voice v27 (th) |
validated − (dev + test) / sentence |
126,690 | 149.41 h |
Google FLEURS (th_th) |
train / transcription |
2,174 | 6.84 h |
n-order/Thai-dialect-corpus |
*_train (all 4 subsets) / sentence |
437,563 | 816.74 h |
huangjianbyte2024/thai_Elderly_Speech_dataset |
train / transcription |
19,198 | 26.56 h |
typhoon-ai/thai-dialect-isan-dataset |
train / isan_spelling |
8,606 | 23.22 h |
| TOTAL | All 5 Datasets Combined | 594,231 | 1,022.78 h |
(1,381 clips from typhoon-ai/thai-dialect-isan-dataset were filtered out because they were $>30\text{s}$ or contained non-Thai characters after number-to-Thai-words conversion.)
Preprocessing & Tokenizer Expansion
- Thai Text Normalization & Cleaning:
- Normalized Thai character order and vowel sequences via
pythainlp.util.normalize(includingํา$\rightarrow$ำ). - Applied community-verified transcript fixes from
thai_commonvoice_dataset(db-lastest.dbandlist_file.tsv).
- Normalized Thai character order and vowel sequences via
- Number-to-Thai-Words Conversion:
- Converted both Thai digits (
๐-๙) and Arabic digits (0-9), comma-formatted numbers, and decimals into spoken Thai words usingpythainlp.util.num_to_thaiwordandpythainlp.util.digit_to_text. - Filtered out any remaining clips containing non-Thai characters or exceeding 30 seconds.
- Converted both Thai digits (
- Word Tokenization:
- Segmented all reference transcripts into space-delimited words using
pythainlp.tokenize.word_tokenize(..., engine="newmm").
- Segmented all reference transcripts into space-delimited words using
- Thai National Corpus (TNC) Vocabulary Expansion (
tokens.txt):- Whistle's base SentencePiece model only covers Latin/European scripts (
en, de, fr, es, it, nl, pl). - We expanded the vocabulary with 2,067 tokens consisting of all 67 base Thai characters (
ก-ฮ, vowels, and tone marks) plus the top 2,000 most frequent Thai words from the Thai National Corpus (pythainlp.corpus.tnc), adding10,958prefix-closed SentencePiece pieces.
- Whistle's base SentencePiece model only covers Latin/European scripts (
Training Progression & Evaluation
Hyperparameters
- Epochs: 3 (
111,420total steps,37,140steps/epoch) - Batch Size: 16
- Optimizer: AdamW (
peak_lr=1e-3, warmup5,571steps, cosine decay to0.0, global grad norm clip1.0) - Hardware: NVIDIA GeForce RTX 4090 (24 GB VRAM)
Epoch-by-Epoch Loss
| Epoch | Step | Train Loss | Validation Loss (Common Voice dev) |
|---|---|---|---|
| Epoch 1 / 3 | 37,140 / 111,420 |
1.3467 |
0.5841 |
| Epoch 2 / 3 | 74,280 / 111,420 |
0.2417 |
0.4239 |
| Epoch 3 / 3 | 111,420 / 111,420 |
0.1803 |
0.3867 |
Evaluation Results
All benchmarks below were evaluated on the exported W4A8 .cact archive using cactus.transcribe(..., language="th").
- Character Error Rate (CER): Computed over Thai characters (whitespace removed).
- Word Error Rate (WER): Computed over space-delimited tokens segmented via
pythainlp(newmm).
Google FLEURS (th_th) Test Set — Comparison with Popular Thai ASR Models (1,021 Clips)
Evaluated on all 1,021 clips of the google/fleurs (th_th) test set alongside popular open-source Thai ASR models via pythaiasr (compare_fleurs.py). All outputs were normalized with the same PyThaiNLP text normalizer, number-to-Thai-words converter, and newmm word tokenizer:
| Model | Architecture / Precision | Model Size | Google FLEURS (th_th) CER |
Google FLEURS (th_th) WER |
Size Ratio vs Ours |
|---|---|---|---|---|---|
MaprangSTT (Ours) |
Cactus Whistle (W4A8 .cact) |
32.5 MB | 13.58% |
31.05% |
1.0× |
openai/whisper-small |
Whisper Small (244M, FP32) |
967.0 MB | 24.21% |
75.74% |
29.8× larger |
airesearch/wav2vec2-large-xlsr-53-th |
Wav2Vec2-Large-XLSR-53 (315M, FP32) |
1,260.0 MB | 9.56% |
35.98% |
38.8× larger |
typhoon_nemotron_asr |
Nemotron-3.5-ASR (0.6B, INT4 ONNX) |
~450.0 MB | 12.28% |
27.52% |
13.8× larger |
biodatlab/whisper-small-th-combined |
Whisper Small Thai (244M, FP16) |
563.0 MB | 9.32% |
24.30% |
17.3× larger |
typhoon_asr |
FastConformer RNN-T (ONNX) | 435.0 MB | 9.02% |
20.11% |
13.4× larger |
Key Takeaways:
- At only 32.5 MB,
MaprangSTToutperformsopenai/whisper-small(967 MB) by 10.63% absolute CER (13.58%vs24.21%) and 44.69% absolute WER (31.05%vs75.74%).- It achieves a 4.93% lower WER (
31.05%vs35.98%) thanairesearch/wav2vec2-large-xlsr-53-thwhile being 38.8× smaller (32.5 MBvs1.26 GB).- It comes within 1.30% CER of the
0.6B-parametertyphoon_nemotron_asr(13.58%vs12.28%) at 1/14th the model size.
Usage
1. Install Cactus
pip install cactus-compute huggingface_hub
2. Transcribe Audio in Python
cactus.transcribe() expects a 16 kHz mono PCM .wav file:
import os
os.environ.setdefault("NEEDLE_TELEMETRY", "0") # needle reports usage unless these are set
os.environ.setdefault("DO_NOT_TRACK", "1")
from huggingface_hub import hf_hub_download
from needle import Whistle
model = Whistle(hf_hub_download("pythainlp/MaprangSTT", "maprang.cact"))
print(model.transcribe("speech.wav", language="th")["text"])
If your input audio is .mp3 or a different sample rate, convert it to 16 kHz mono .wav first (e.g., with ffmpeg or soundfile + soxr):
import subprocess
import tempfile
import cactus
def transcribe_any_audio(audio_path: str, weights_path: str = "maprang.cact") -> str:
with tempfile.NamedTemporaryFile(suffix=".wav") as tmp_wav:
subprocess.run(
["ffmpeg", "-y", "-i", audio_path, "-ar", "16000", "-ac", "1", tmp_wav.name],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
)
res = cactus.transcribe(tmp_wav.name, weights=weights_path, language="th")
return res["text"].strip()
3. Rebuild .cact Archive from the LoRA Adapter
If you want to build a platform-specific runnable bundle or slice decoder layers (--layers 4 or --layers 6) from adapter.safetensors:
pip install "cactus-compute[train]"
# Build full 8-layer standalone .cact archive
cactus whistle build \
--lora adapter.safetensors \
--out model.cact
# Or build a smaller/faster 4-layer decoder archive
cactus whistle build \
--lora adapter.safetensors \
--layers 4 \
--out model-l.cact
Repository Files
maprang.cact(32.49 MB): Standalone quantized (W4A8) Whistle model archive ready forcactus.transcribe().adapter.safetensors(29.0 MB): Trained LoRA weights (rank=16,alpha=32) and expanded Thai vocabulary embedding rows.tokens.txt(2,067lines): Base Thai characters and top 2,000 TNC words used to expand the SentencePiece tokenizer.
Citation & Acknowledgements
- Base Model & Framework: Cactus Compute — Finetuning Whistle
- NLP Preprocessing & Corpus: PyThaiNLP & thai_commonvoice_dataset
- Speech Datasets:
Model tree for pythainlp/MaprangSTT
Base model
Cactus-Compute/whistle