Gidi: a 29 MB on-device model for Vietnamese personal-finance notes
A 29 MB INT8 model that reads short Vietnamese money notes and predicts type, counterparty and amount, running fully in the browser with no server.
Gidi ("GiaoDịch") is an experimental machine learning project I started in 2026. It's how I'm learning ML, and I'm building it alone. The model is tiny, runs on the device, and reads short, informal Vietnamese money notes like ăn phở 45k, nhận lương 15tr or mượn chú hai 5 xị. The demo is live at gidi.8bu.dev.
What does it predict?
For each note it gives back three things. The first is the transaction type, one of expense, income, borrow, lend, repayment_in, repayment_out, transfer or refund. The second is the counterparty, the span of text that says who the money was with. The third is the amount, as the span was written. Turning that span into a number is plain deterministic code, not something the model guesses.
How is v3 built?
The current model is gidi-finance-v3, release 3.0.0. One INT8 encoder (4 layers, 768 dims, vocabulary pruned to 8,338 tokens) sits in a single ONNX graph and predicts the type and the counterparty. A small "word snap" step then stretches the counterparty span out to whole-word boundaries. The amount comes from a rule-based parser, gidi.value_parser, instead of a learned head. I prefer deterministic over generative where I can, and the amount is a place where I can: the rules read 45k or 15tr the same way every time.
How big and how fast is it?
It's a 29.3 MB INT8 ONNX file, and it runs in about 1.4 ms p50 on one CPU thread (Apple Silicon, batch 1). That's half the size of v2, which was a 58.1 MB dual encoder with a CRF value head at 3.5 ms p50. v1 only did type and counterparty, at 28.66 MB.
How good is it?
I measured it on a test set called human-value-02, 190 notes. Be careful with these numbers: the labels on that set came from an LLM, not from a person, and the results are preliminary. On it v3 gets 92.1% on type, 85.3% on counterparty, 100% on amount and 78.9% end to end. v2 on the same set got 75.8%, 74.7%, 98.4% and 59.5%.
What are the limits, and what's next?
Multi-word brand and shop names get cut short, so lotte cinema becomes lotte. Gift and lì xì notes are weak, and so are some notes where the direction of a debt is unclear. The confidences aren't calibrated, and the model can't yet say "I don't know". Next I want to raise the counterparty accuracy, calibrate the confidences and add abstention.
How was the data labelled?
I generated a raw corpus, then reviewed and labelled it by hand in Quet, a separate open-source terminal tool of mine ("Quick Utility for Evaluating Text", github.com/8bu/quet). AI proposed labels, and I rechecked them. The datasets are versioned JSONL files with frozen test sets.
What is it built with?
Training is Python 3.12 with uv, PyTorch (Apple MPS is the main device), Hugging Face Transformers, Tokenizers and Datasets, scikit-learn for evaluation and baselines, and pytest plus ruff. The deploy path is PyTorch to ONNX (INT8) to ONNX Runtime, and the Python runtime only needs numpy, onnxruntime and tokenizers. The demo is Vite, React 19, shadcn/ui and Tailwind v4 with onnxruntime-web, deployed on Cloudflare. There's no cloud training, no Docker and no notebooks-first workflow.
Where can I try it or get it?
There's no server and no inference API. The demo at gidi.8bu.dev is a notes app, and it works on phones and tablets. It runs the model fully in your browser with onnxruntime-web (WASM), and your notes land on a pile per type with running totals. Developer tools are at gidi.8bu.dev/lab. The model is on Hugging Face at huggingface.co/x8bu/gidi-finance (INT8, Apache-2.0), the GitHub releases carry INT8 and FP32 archives, and the code is at github.com/8bu/gidi.