--- language: - vi license: mit library_name: onnx pipeline_tag: translation tags: - text2text-generation - onnx - vietnamese - teencode - text-normalization - seq2seq - int8 - raspberry-pi model-index: - name: TTS-standardlization results: - task: type: text2text-generation name: Vietnamese teencode normalization dataset: type: custom name: Synthetic Vietnamese teencode pairs (held-out test sets) metrics: - {type: cer, value: 2.08, name: CER original teencode test (999 rows, INT8, beam 4 + guard)} - {type: cer, value: 11.19, name: CER untoned test (986 rows, INT8)} - {type: cer, value: 4.95, name: CER hard test (1251 rows, eval-only, INT8)} - {type: cer, value: 0.35, name: "CER context test (803 rows, INT8; in-distribution, optimistic)"} --- # teencode-normalizer v3 Encoder-decoder Transformer that rewrites Vietnamese **teencode** (abbreviations, missing tones, slang spellings, short pronouns) into standard Vietnamese. Small enough (~20.8 MB INT8) to run on a Raspberry Pi 4 with ONNX Runtime. Code (Rust data prep / inference / HTTP server, Python training, Pi Docker, Colab): https://github.com/femboyvn/TFVN-TTS-standardlization ## Files | Path | Content | |---|---| | `onnx/encoder.int8.onnx`, `onnx/decoder.int8.onnx` | INT8 dynamic-quantized ONNX (no KV cache) | | `onnx/tokenizer.json` | shared BPE tokenizer, 8,000 tokens, NFC | | `onnx/output_vocab.txt`, `onnx/guard.json` | word-alignment guard data/settings used by the runner | | `onnx/config.json` | export config | | `model.safetensors` | PyTorch weights (FP16, weights only, step 13,000) | | `config.json` | model hyperparameters | | `inference_onnx.py` | minimal Python ONNX Runtime example (greedy) | ## Model details - Transformer seq2seq, pre-LayerNorm: **8 encoder + 4 decoder layers**, d_model 256, 8 heads, d_ff 1536, dropout 0.1, **~19.94M parameters**. - Shared BPE tokenizer, **8k vocab**, NFC normalization; max length 192 tokens. Special ids: PAD 0, BOS 1, EOS 2, UNK 3. - Trained with PyTorch (BF16 autocast, AdamW, cosine schedule); grown from a smaller v2 model with function-preserving initialization. Selected checkpoint: step 13,000 (best combined dev CER). - Recommended decoding: beam 4 + word-alignment guard (`guard.json`). ## Intended use - Normalizing informal Vietnamese chat / social-media text before search, NLP pipelines, TTS or display. - On-device / edge use (Raspberry Pi 4 class hardware). **Out of scope:** translation, content moderation, free text generation. The model rewrites; it does not filter. ## Evaluation Character error rate (lower is better), beam 4 + guard: | Test set | v2 | v3 INT8 | v3 BF16 | v3 FP32 | |---|---|---|---|---| | Original teencode (999) | 2.92% | 2.08% | 2.05% | 2.03% | | Untoned (986) | 12.38% | 11.19% | 11.37% | 11.31% | | Context (803, in-distribution) | 11.93% | 0.35% | 0.23% | 0.22% | | Hard (1,251, eval-only) | 10.96% | 4.95% | 4.84% | 4.85% | | Hard: ambiguous abbreviations | 13.59% | 8.09% | 7.94% | 7.94% | | Hard: pronoun pairs | 9.45% | 2.34% | 2.22% | 2.24% | | Hard: untoned context | 10.04% | 6.20% | 6.22% | 6.22% | The context test set comes from the same generator as the context training data, so its score is optimistic. The hard set has no overlap with the training data and is the fairer measure. ## Examples (v3 INT8, beam 4 + guard) | Input | Output | |---|---| | c oi, e nho c qua | chị ơi, em nhớ chị quá | | c oi, c muon hoi cai nay | chú ơi, cháu muốn hỏi cái này | | co oi, c xin phep nghi hoc | cô ơi, cháu xin phép nghỉ học | | mua toi goi cc cao cap voi | mua tôi gói claude code cao cấp với | | ong oi, t mua them cc | ông ơi, tôi mua thêm cái | | b oi, c ve roi nha | bạn ơi, cháu về rồi nha | | thang lam ML | thằng làm ML | ## How to run ### Rust runner (recommended: beam 4 + guard) ```bash git clone https://github.com/femboyvn/TFVN-TTS-standardlization && cd TFVN-TTS-standardlization huggingface-cli download tfvn/TTS-standardlization --include "onnx/*" --local-dir hf cargo build --release export ORT_DYLIB_PATH=/path/to/libonnxruntime.so # ARM64 build on the Pi ./target/release/step_4_inference --model-dir hf/onnx "c oi, e nho c qua" ``` The GitHub repo also has a Raspberry Pi Docker image with an HTTP endpoint (`POST /normalize`). ### Python (ONNX Runtime, greedy, no guard) ```bash pip install onnxruntime tokenizers numpy huggingface_hub huggingface-cli download tfvn/TTS-standardlization --local-dir teencode-v3 cd teencode-v3 && python inference_onnx.py "c oi, c muon hoi cai nay" # chú ơi, cháu muốn hỏi cái này ``` Core of `inference_onnx.py`: ```python import numpy as np, onnxruntime as ort from tokenizers import Tokenizer BOS, EOS = 1, 2 enc = ort.InferenceSession("onnx/encoder.int8.onnx") dec = ort.InferenceSession("onnx/decoder.int8.onnx") tok = Tokenizer.from_file("onnx/tokenizer.json") def normalize(text): src = np.array([[BOS] + tok.encode(text, add_special_tokens=False).ids[:190] + [EOS]], dtype=np.int64) mem = enc.run(None, {"src_ids": src})[0] ys = [BOS] for _ in range(min(192, src.shape[1] + 24) - 1): logits = dec.run(None, {"tgt_ids": np.array([ys], dtype=np.int64), "memory": mem, "src_ids": src})[0] nxt = int(logits.reshape(-1, logits.shape[-1])[-1].argmax()) if nxt == EOS: break ys.append(nxt) return tok.decode(ys[1:]) ``` Greedy decoding without the guard is simpler but slightly less accurate than the Rust runner (it can occasionally produce a malformed word). ### PyTorch weights `model.safetensors` loads into the `Seq2Seq` class in `scripts/step_2_train.py` from the GitHub repo: ```python import json, step_2_train as S from safetensors.torch import load_file cfg = {k: v for k, v in json.load(open("config.json")).items() if k in S.CFG} m = S.Seq2Seq(cfg); m.load_state_dict({k: v.float() for k, v in load_file("model.safetensors").items()}) ``` Use these to fine-tune further (`--init-from`) or to re-export ONNX. ## Limitations and risks - **Vulgar output is intended:** vulgar slang in the input is normalized to the full vulgar words; the model does not censor. - Ambiguous abbreviations can be resolved wrongly, e.g. "cc" sometimes becomes "cái" instead of "claude code". - "ml" depends on capitalization: "ML" stays machine learning, lowercase "ml" tends to get the vulgar reading; a capitalized word like "Lam" may be read as a name (Lâm). - Brand names are output in lowercase. - Fully untoned input remains hard (~11% CER). - Trained only on synthetic data; real-world distribution shift is likely. **The training data is not released.** - Raspberry Pi 4 latency (0.25–0.55 s per sentence, INT8) is an estimate and has **not been measured** on a Pi. ## License MIT (see `LICENSE`).