ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
무료 Lun Bawang 번역기와 사전
Free Lun Bawang translator and Lun Bawang dictionary — translate Lun Bawang to English or Lundayeh to English. AI model fine-tuned on the first parallel corpus for this language of Borneo.
교육Python · HTMLGitHub @matteuspan온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Translator for Lunbawang
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A bidirectional Lun Bawang ↔ English machine translator, built by fine-tuning language models on the first parallel corpus ever assembled for the language. The web interface is deployed on Render and runs against a fine-tuned model served via the Tinker API. The production default is the v2 dictionary retrain — Inkling-Small · Step 16,000, warm-started from the earlier Inkling checkpoint-11,500 and continued on the OCR'd Kemaloh Lundayeh–English dictionary (22,038 pairs). See Evaluation for how it was selected and Past experiments and versions for the earlier Qwen3-8B and Inkling runs.README 전문 보기 ↗Live demo: https://translate.lunbawang.com/
A bidirectional Lun Bawang ↔ English machine translator, built by fine-tuning language models on the first parallel corpus ever assembled for the language. The web interface is deployed on Render and runs against a fine-tuned model served via the Tinker API. The production default is the v2 dictionary retrain — Inkling-Small · Step 16,000, warm-started from the earlier Inkling checkpoint-11,500 and continued on the OCR'd Kemaloh Lundayeh–English dictionary (22,038 pairs). See Evaluation for how it was selected and Past experiments and versions for the earlier Qwen3-8B and Inkling runs.
Lun Bawang (also called Lun Dayeh or Lundayeh) is an Austronesian language spoken by approximately 50,000–80,000 people across the highlands of Borneo — primarily in Sarawak (Malaysia), Kalimantan (Indonesia), and Brunei. It is severely under-resourced: no public parallel corpus existed before this project.
Browser (static/index.html)
│ POST /api/translate
▼
FastAPI server (serve.py)
│ OpenAI-compatible REST call
▼
Tinker inference API
└─ Inkling-Small + LoRA (v2 dictionary retrain, checkpoint-16000, production default)
<think>…</think> reasoning blocks are stripped before the response is returned. Every Inkling call passes reasoning_effort="medium" — see The empty-output issue for why medium is the floor, and why a single retry at "high" is issued if a call comes back empty.The server maintains a curated set of ~250 common English function words, time words, nouns, verbs, and adjectives. If the input contains ≥2 English words, or ≥25% of its tokens match, it is classified as English (→ translate to Lun Bawang). Otherwise it is treated as Lun Bawang (→ translate to English). The user can override this at any time using the swap button.
Long English sentences are split on commas, semicolons, and coordinating conjunctions (and / but / or / so / yet / nor). Conjunctions only trigger a split when both sides have ≥3 words, preventing over-splitting of short phrases like "bread and butter". Each clause is sent to the model separately, and both the whole-sentence and clause-level translations are shown side by side.
The training corpus combines a large Biblical parallel text with several smaller, higher-diversity everyday-language sources, plus the OCR'd Kemaloh Lundayeh dictionary added for the v2 retrain.
The only substantial public Lun Bawang text available is the full Bible translation published by the Sabah Theological Seminary. The PDF (LunBawang-Bible.pdf) was parsed with parse_lun_bawang.py into verse-level segments, then aligned verse-by-verse with the World English Bible (WEB, public domain) using build_parallel_corpus.py, keyed on book code + chapter + verse. The result is parallel_corpus.csv (~30,000 matched verse pairs across 66 books).
Train / val split: 90% train / 10% val, stratified by Bible book.
A ~400-page scanned dictionary — Ganang, Crain & Pearson-Rounds, Kemaloh Lundayeh–English Dictionary — was OCR'd into structured entries via the Anthropic Batch API (Claude vision, forced tool-use into a strict entry schema: headword, homograph, senses, examples, variants, cross-references), then flattened into the corpus's source,lun_bawang,english,type schema by corpus/build_dictionary_corpus.py. The result is corpus/dictionary_corpus.csv with four row types:
| type | count | used for |
|---|---|---|
word | 7,478 | headword ↔ gloss (both directions) |
definition | 11,184 | headword → descriptive English gloss (lb→en only; a definition is a poor en→lb target) |
sentence | 2,281 | example sentences (both directions, up-weighted 3×) |
redirect | 1,095 | "see X" cross-references (dropped from training) |
This is the source that shifts the model from Bible-only fluency toward everyday vocabulary. See Evaluation for how it is weighted and gated.
Word-level pairs from borneodictionary.com/lun-bawang/, parsed from repeating LB_HEADWORD / English: … / Bahasa Malaysia: … blocks by build_aux_corpus.py.
A language-learning page from longsemadoh.wordpress.com using five different inline formats simultaneously (dialogue blocks, alternating EN/LB lines, numbered sentences, parenthetical LB, dash/equals-separated pairs). The parser handles all five and uses an English-word scoring heuristic to orient each pair. Entries with ≥3 Lun Bawang words are classified as sentence, shorter ones as word.
Appendix A.1 of Mortensen's PhD dissertation "The Kemaloh Lun Bawang Language of Borneo" (University of Hawai'i) contains a Mouse-deer vs. Crocodile fairy tale in two-column parallel format — narrative dialogue, the domain farthest from the Biblical data. parse_mortensen.py uses pdfminer coordinate-based column separation to extract and align paragraph-level pairs. Used for non-commercial research purposes.
build_aux_corpus.py combines sources 3, 4, and 5 into aux_corpus.csv (columns: source, lun_bawang, english, type). Split 80% train / 20% val, randomised per source. The dictionary corpus (source 2) is loaded and split separately (see Evaluation).
Tinker is a hosted fine-tuning and inference service. It provides a Python SDK (tinker) and an OpenAI-compatible REST API for serving.
Base model: thinkingmachines/Inkling-Small (current production) or Qwen/Qwen3-8B (archived v0/v1 runs), selected with --base-model
Adaptation: LoRA, rank 16 · Optimiser: Adam, lr 5e-5 · Batch size: 8 · Sequence length cap: 384 tokens
export TINKER_API_KEY=your_key_here
The training client is created with ServiceClient().create_lora_training_client(...). Each saved checkpoint has a tinker://… URI passed directly to the OpenAI-compatible inference endpoint as the model parameter.
Every source pair produces datums using the model's chat template (native TML rendering for Inkling; the generic HF template for Qwen):
[system] You are a translator specializing in the Lun Bawang language …
[user] Translate to English:\n{lun_bawang_text}
[assistant] {english_text}
Only the assistant tokens contribute to the loss. Serving (serve.py) and eval use this exact instruction verbatim — a bare Translate to English: / Translate to Lun Bawang: — because any extra wording (e.g. calling a single word a "sentence", or appending an "output only the translation" hint) is out-of-distribution and reliably triggers empty completions on hard single words.
# Current recipe (Inkling base, dictionary corpus folded in):
python3.13 train_translator.py --train \
--base-model thinkingmachines/Inkling-Small \
--state-file tinker_state_inkling_v2.json
The script resumes automatically from the last checkpoint if the target state file exists, and full-resumes optimizer state where available. --base-model thinkingmachines/* requires pip install tml-renderers torch (these models use a native TML chat format, not the generic HF chat template). The v2 run was warm-started from Inkling checkpoint-11,500 (weights only; optimizer reset, with a 500-step LR warmup).
python3.13 train_translator.py --translate --direction en2lb --text "In the beginning"
Validation metrics are computed during training and written back to the state file. A fast val_loss (teacher-forced cross-entropy, no sampling) runs every 100 steps; the full bidirectional BLEU / exact-match / chrF eval runs every 2,000 steps against fixed, seeded held-out sets. All sampling-based eval uses reasoning_effort="medium", matching serving.
The v2 run warm-starts from Inkling checkpoint-11,500 and continues for one epoch over the combined Bible + auxiliary + dictionary corpus. Recipe decisions (made with the maintainer):
word/sentence → both directions; definition → lb→en only; redirect → dropped.(lb, en) pair equals a held-out probe in any corpus.dictword/dictsent sets (both directions), seeded identically to the trainer's split so they are exactly the model's held-out rows. Word recall is scored with chrF (partial credit) alongside exact-match, because a single-word gold answer rarely matches token-for-token even when the meaning is right.Selection. The goal was everyday English↔Lun Bawang quality, not literal Bible fidelity, so checkpoints were ranked by an everyday-quality blend — dict-sentence BLEU (both directions) + general-sentence BLEU + 0.1·dict-word chrF — excluding only any checkpoint where Bible BLEU catastrophically collapsed (a gibberish cliff; a moderate drop toward conversational register is expected and fine). The final call was made at reasoning_effort="medium" because at low a large fraction of single-word probes return empty completions and score zero, understating the dictionary metrics (see below).
Contender comparison (medium effort):
| Step | dict-sent lb→en | dict-sent en→lb | sentence lb→en | dict-word en→lb chrF | Bible lb→en | blend |
|---|---|---|---|---|---|---|
| 8,000 | 13.3 | 7.3 | 24.6 | 24.6 | 60.2 | 47.0 |
| 12,000 | 12.3 | 10.5 | 25.2 | 24.8 | 59.3 | 49.6 |
| 14,000 | 12.7 | 3.6 | 26.6 | 20.6 | 57.2 | 44.5 |
| 16,000 | 12.6 | 10.0 | 27.9 | 27.1 | 58.9 | 52.3 |
Step 16,000 is the best checkpoint of the run — strongest general lb→en sentences and en→lb dictionary quality, with Bible fidelity holding (~57 BLEU, no collapse). It is now the served default; warmup, the phrasebook shortcut, and the dropdown "(default)" marker all follow it.
Training trajectory (auto-evals, low effort — the curve, not the final selection):
| Step | Val loss | Bible lb→en | dict-sent lb→en | dict-sent en→lb | dict-word chrF (lb / en) | Sent lb→en |
|---|---|---|---|---|---|---|
| 2,000 | 0.0081 | 64.1 | 12.9 | 1.9 | — | 26.5 |
| 4,000 | 0.0054 | 57.6 | 11.9 | 5.9 | — | 26.2 |
| 6,000 | 0.0048 | 66.3 | 10.4 | 7.8 | 17.0 / 20.5 | 24.8 |
| 8,000 | 0.0055 | 60.2 | 14.3 | 8.6 | 17.0 / 24.0 | 24.5 |
| 10,000 | 0.0022 | 64.5 | 12.4 | 8.8 | 17.4 / 25.2 | 22.5 |
| 12,000 | 0.0018 | 59.3 | 11.9 | 9.3 | 16.8 / 25.9 | 24.8 |
| 14,000 | 0.0021 | 57.2 | 13.3 | 7.0 | 16.2 / 16.5 | 26.6 |
The en→lb direction improved steadily across the run — the direction that was weakest and matters most for everyday use. Dictionary single-word exact-match stays low (~2–13%) throughout because single-word gold answers rarely match token-for-token; chrF is the metric to watch there. Raw per-translation outputs for every eval run live in eval/eval_raw/*.jsonl (merged into eval/eval_outputs.csv).
Inkling supports a reasoning_effort control. Training never includes an effort signal, so any value is somewhat out-of-distribution — and at low effort certain short inputs (single common words, short idioms) come back as a single end-of-message token with zero generated content rather than a wrong-but-present translation. Measured on 50 single-word lb→en probes:
| checkpoint | low | medium |
|---|---|---|
| v1 · checkpoint-11,500 | 4% | 0% |
| v2 · checkpoint-16,000 | 36% | 0% |
The dictionary retrain did not fix this at the model level — v2 is actually more empty-prone at low, even though single words are what it trained on. At medium both collapse to 0%. Mitigation shipped to production (serve.py): every input starts at reasoning_effort="medium", with a single retry at "high" on the rare empty. Eval uses medium too, so its numbers reflect what the served model actually does.
All BLEU scores are sacrebleu corpus BLEU on fixed held-out sets. Bidirectional eval uses the same 50 Bible, 144 dictionary, and 40 sentence examples per checkpoint.
42a2c780, 16,000 steps)| Step | Val loss | Bible LB→EN | Bible EN→LB | Dict LB→EN | Dict EN→LB | Sent LB→EN | Sent EN→LB |
|---|---|---|---|---|---|---|---|
| 8,000 | 0.185 | 54.55 | 44.06 | 20.1% | 16.7% | 34.98² | 1.24 |
| 16,000 | 0.054 | 58.24 | — | 19.5% | — | 33.59¹ | — |
¹ Unidirectional (LB→EN only) eval; earlier eval code. ² Measured on the original sentence val set (longsemadoh only, before Mortensen prose was added in v1); with the current val set the score is 5.99.
Training stopped at step 16,000 (mid-epoch 3); val loss kept falling into epoch 3, indicating overfitting to Biblical register. Step 8,000 gave the best balance.
Comparison with GPT models (v0 val set):
| Model | Bible BLEU | Dict exact | Sentence BLEU | Avg ms/call |
|---|---|---|---|---|
| Our model (v0·checkpoint-8000) | 51.70 | 20.3% | 30.79 | ~5,400 |
| gpt-4o | 10.44 | 6.0% | 21.46 | ~716 |
| gpt-5-mini | 8.79 | 3.8% | 10.95 | ~18,300 |
| gpt-4o-mini | 7.22 | 2.3% | 11.84 | — |
The fine-tuned model scores ~5× higher on Bible BLEU and ~3× higher on Dict exact-match than the best general GPT model, confirming that Lun Bawang vocabulary is largely absent from general pre-training data.
719fbcd8, 11,500 steps)v1 adds Mortensen (2021) narrative sentences and a small amount of user feedback. Sentence BLEU split by reference length: sh ≤10 words, lg >10 words.
| Step | Val loss | Bible LB→EN | Bible EN→LB | Dict LB→EN | Dict EN→LB | Sent LB→EN (sh / lg) | Sent EN→LB (sh / lg) |
|---|---|---|---|---|---|---|---|
| 2,000 | 0.734 | 27.83 | 20.09 | 13.9% | 7.6% | 8.64 (13.2 / 7.6) | 6.33 (1.4 / 7.1) |
| 4,000 | 0.454 | 42.35 | 24.10 | 16.0% | 7.6% | 14.99 (15.3 / 14.7) | 9.65 (1.6 / 11.4) |
| 6,000 | 0.324 | 50.45 | 30.85 | 13.9% | 1.4%† | 15.70 (15.7 / 15.4) | 8.96 (3.1 / 9.9) |
| 8,000 | 0.173 | 48.49 | 47.47 | 22.2% | 12.5% | 14.90 (18.6 / 13.6) | 3.43 (5.3 / 3.5) |
| 10,000 | 0.147 | 46.88 | 46.70 | 23.6% | 16.7% | 21.72 (20.1 / 22.4) | 11.04 (2.3 / 12.8) |
| 11,500 | — | 58.82 | 56.48 | 24.3% | 15.3% | 22.07 (20.3 / 22.2) | 9.51 (4.2 / 10.3) |
† Step 6,000 EN→LB dict anomaly: model was emitting <think> tokens at that checkpoint. v1 · Step 11,500 was the best Qwen checkpoint, superseded by Inkling.
Inkling-Small is Thinking Machines' small reasoning model, available on Tinker at lower per-token cost than Qwen3-8B. It was fine-tuned on the exact same corpus, LoRA config, and step count (11,500) as the archived v1 Qwen run, for a direct step-matched comparison. The v2 dictionary retrain warm-starts from this run's checkpoint-11,500.
Inkling uses a native TML chat format (tml_renderers) rather than a generic HF chat template. Training and inference prompts must be rendered through tml_renderers.Renderer, or the fine-tuned model produces empty output through the real serving API even though in-loop training-time eval looks fine. train_translator.py and eval/eval_checkpoint.py both auto-detect thinkingmachines/* base models and switch rendering paths accordingly.
| Step | Val loss | Bible LB→EN | Bible EN→LB | Dict LB→EN | Dict EN→LB | Sent LB→EN | Sent EN→LB |
|---|---|---|---|---|---|---|---|
| 2,000 | 0.465 | 48.68 | 27.73 | 17.4% | 12.5% | 20.36 | 6.92 |
| 4,000 | 0.224 | 52.25 | 45.43 | 18.1% | 14.6% | 16.64 | 7.98 |
| 6,000 | 0.125 | 45.88 | 54.96 | 18.8% | 22.2% | 19.51 | 11.92 |
| 8,000 | 0.089 | 58.14 | 61.97 | 9.7% | 13.2% | 23.74 | 3.12 |
| 10,000 | 0.051 | 66.46 | 67.00 | 16.0% | 12.5% | 18.87 | 5.75 |
| 11,500 | 0.044 | 59.11 | 71.59 | 17.4% | 18.8% | 24.57 | 9.85 |
| Metric | Inkling-Small | Qwen v1 | Winner |
|---|---|---|---|
| Bible BLEU LB→EN | 59.11 | 58.82 | ~tied |
| Bible BLEU EN→LB | 71.59 | 56.48 | Inkling (+15.1) |
| Dict exact LB→EN | 17.4% | 24.3% | Qwen (+6.9pp) |
| Dict exact EN→LB | 18.8% | 15.3% | Inkling (+3.5pp) |
| Sent BLEU LB→EN | 24.57 | 22.07 | Inkling (+2.5) |
| Sent BLEU EN→LB | 9.85 | 9.51 | ~tied |
Inkling-Small won or tied on 5 of 6 metrics and became the production default before the v2 dictionary retrain superseded it.
A Bible BLEU above 50 for a rare language with ~30k training sentences is strong — Google Translate achieves ~40 BLEU for well-resourced pairs like French→English. Published results on similarly low-resource languages typically fall in the 20–35 range at equivalent data scales. The ceiling is partly set by reference quality and the domain gap: training data is primarily Biblical, while the sentence/dictionary eval sets are conversational and lexical.
The web UI collects thumbs up/down feedback on every translation, feeding future fine-tuning runs.
Each entry records source text, direction, checkpoint, model output, rating, and correction. IPs are truncated to the /24 prefix before being written anywhere.
Sole store: eval/feedback.csv in the repo — if GITHUB_TOKEN is set, every submission triggers an async commit that appends the row. No local database; data survives Render redeploys.
python3.13 eval/review_feedback.py --dry-run # summary + QC flags
python3.13 eval/review_feedback.py --csv eval/feedback.csv # writes feedback_corpus.csv
The QC script filters no-ops, self-copies, and empty corrections, and flags unusual submission volume. Output feedback_corpus.csv uses the same schema as aux_corpus.csv. train_translator.py loads it automatically and repeats feedback entries 10× (vs. 5× for aux) as high-confidence human signal.
pip install -r requirements.txt
requirements.txt includes: fastapi, uvicorn, openai, sacrebleu, requests, beautifulsoup4.
export TINKER_API_KEY=your_key_here
python3.13 serve.py # → http://localhost:8000 (use --port to change)
The server reads tinker_state.json (and the per-run state files) to discover checkpoints. If none exist yet, the UI shows a "training in progress" notice.
python3.13 corpus/parse_lun_bawang.py # 1. Parse the Lun Bawang Bible PDF into verses
python3.13 corpus/build_parallel_corpus.py # 2. Align with World English Bible
python3.13 corpus/parse_mortensen.py # 3. Parse the Mortensen (2021) appendix (needs the PDF)
python3.13 corpus/build_aux_corpus.py # 4. Build the auxiliary word/sentence corpus
python3.13 corpus/build_dictionary_corpus.py # 5. Flatten OCR'd dictionary entries → dictionary_corpus.csv
python3.13 train_translator.py --train --base-model thinkingmachines/Inkling-Small \
--state-file tinker_state_inkling_v2.json # 6. Train (requires TINKER_API_KEY)
Dictionary OCR itself (scans → corpus/dictionary_entries.jsonl) is run by corpus/ocr_dictionary.py against the Anthropic Batch API and needs ANTHROPIC_API_KEY plus the page images.
The app is deployed on Render as a web service; on startup Render runs python3.13 serve.py. TINKER_API_KEY is a Render environment variable. The committed state files (tinker_state*.json) tell the server which checkpoints exist and where to find them on Tinker — no model weights are stored in the repo.
The checkpoint dropdown lets you compare any saved checkpoint across every run. v2 · Step 16,000 is pre-selected as the default.
lunbawang-translate/
├── serve.py # FastAPI web server + translation API + phrasebook
├── train_translator.py # Fine-tuning + in-loop evaluation
├── orchestrate_retrain.py # Recycle-proof driver used to run the v2 retrain on an ephemeral host
├── cleanup_checkpoints.py # Utility for pruning Tinker checkpoints
├── tinker_state.json # Checkpoint metadata for the current run
├── tinker_state_v1.json # Archived Qwen3-8B v1 run
├── tinker_state_inkling.json # Inkling-Small v1 (step-matched 11,500 run)
├── tinker_state_inkling_v2.json # Inkling v2 dictionary retrain — production default (step 16,000)
├── feedback_corpus.csv # Generated by eval/review_feedback.py; loaded by training
├── requirements.txt
├── static/index.html # Single-page frontend
│
├── corpus/ # Corpus building: source files, scripts, output data
│ ├── parse_lun_bawang.py # Extract verses from LunBawang-Bible.pdf
│ ├── build_parallel_corpus.py # Align LB Bible verses with WEB English
│ ├── parse_mortensen.py # Extract parallel pairs from the Mortensen (2021) PDF
│ ├── build_aux_corpus.py # Parse borneodict.txt + longsemadoh.txt + Mortensen
│ ├── ocr_dictionary.py # OCR the Kemaloh Lundayeh dictionary scans (Anthropic Batch API)
│ ├── build_dictionary_corpus.py # Flatten OCR'd entries → dictionary_corpus.csv
│ ├── parallel_corpus.csv # ~30k Bible verse pairs
│ ├── aux_corpus.csv # ~800 word/sentence pairs from web + Mortensen
│ ├── dictionary_corpus.csv # 22,038 pairs from the Kemaloh Lundayeh dictionary
│ └── dictionary_entries.jsonl # Structured OCR output (pre-flatten)
│
└── eval/ # Evaluation + feedback review scripts and outputs
├── eval_checkpoint.py # Standalone bidirectional BLEU/exact/chrF eval for a checkpoint
├── eval_openai.py # Eval any OpenAI model on the same val set
├── merge_evals.py # Merge eval_raw/*.jsonl → eval_outputs.csv
├── review_feedback.py # QC + prepare feedback_corpus.csv
├── feedback.csv # Sole feedback store
├── eval_outputs.csv # Combined eval results across all runs
└── eval_raw/*.jsonl # Per-run raw model outputs (one JSON object per translation)
medium effort; the server retries once at high, and a repeat attempt almost always succeeds.