--- license: mit language: - en - es - fr - de library_name: onnx tags: - time - temporal-parsing - date-parsing - nlp - sql - multilingual - onnx - sundial --- # sundial-encoder-full **Time expressions (en / es / fr / de) → a portable IR → SQL / JS date ranges.** A tiny (~14 MB) multi-head classifier that runs in the browser or on the server in a single forward pass. - 💻 **Code (MIT):** https://github.com/ceritium/sundial - 🕹️ **Live demo:** https://sundial.onvibe.run/playground - 📊 **Benchmarks:** https://github.com/ceritium/sundial/blob/master/BENCHMARKS.md ## What it does Given a phrase like `"last 7 days"`, `"último miércoles de 2027"`, `"first day of the month"` or `"a fortnight ago"`, the model emits an **IR skeleton** — the *structure* only, no digits (`last_n(N, day)`, `nth_weekday(last, wed, YEAR)`, `nth_day(1, month, this)`, `offset(-N, week)`, …). A small **deterministic layer** (in the repo) then extracts the exact numbers, months and dates from the input and materializes the full IR, which renders to a bound **SQLite / Postgres predicate** or a JS `{start, end}` range. > The model never generates a digit. Because the output space is a **finite set of skeletons**, this > is **classification, not generation** — so a 14 M-parameter encoder with a few heads is enough, and > it generalizes to years and phrasings it barely saw. ## Model - Base: `google/electra-small-discriminator` (~14 M params), fine-tuned as a **multi-head classifier** with one head per IR slot: `op / unit / which / dow / ord / scope / sign / has_at`. - Format: **int8 ONNX**, ~14 MB. Runtime: `onnxruntime` (WASM in the browser, single pass, ~1 ms/inf CPU). - Trained on `en + es + fr + de`, basic + complex expressions, plus **LLM-distilled paraphrases** for phrasing robustness. **Files:** `model_int8.onnx`, the tokenizer, `heads.json` (the per-head label vocabulary). ## Highlights - Matches a T5 seq2seq (98.6% gold) at **14 MB** and ~30× faster per inference. - One 14 MB English-vocab base handles **en/es/fr/de** at ~99% (a 135 MB multilingual base adds ~1%). - vs rule engines: a *tie* with Duckling on the constructs it supports, but wins on **out-of-distribution phrasing** (91.7% vs 38.9% on paraphrases) — the distilled model generalizes the long tail. ## Usage The model outputs the 8 head logits; you `argmax` them, compose the skeleton, then run the deterministic `materialize` + renderers from the repo. The repo ships a ready-to-run script: ```bash git clone https://github.com/ceritium/sundial && cd sundial uv run --project train python train/predict_onnx.py \ --hf ceritium/sundial-encoder-full \ --inputs data/gold/gold_complex_en.jsonl --out models/pred.jsonl node eval/run_eval.mjs --gold data/gold/gold_complex_en.jsonl --predictions models/pred.jsonl --skeleton # -> 26 examples · 100.0% (skeleton -> materialize -> execute vs gold) ``` Minimal inference (Python + onnxruntime): ```python import json, numpy as np, onnxruntime as ort from huggingface_hub import hf_hub_download from transformers import AutoTokenizer repo = "ceritium/sundial-encoder-full" tok = AutoTokenizer.from_pretrained(repo) heads = json.load(open(hf_hub_download(repo, "heads.json"))) sess = ort.InferenceSession(hf_hub_download(repo, "model_int8.onnx")) enc = tok("last wednesday of 2027".lower(), return_tensors="np", truncation=True, max_length=32) logits = sess.run(heads["head_names"], {"input_ids": enc["input_ids"].astype(np.int64), "attention_mask": enc["attention_mask"].astype(np.int64)}) slots = {n: heads["heads"][n][int(np.argmax(logits[i][0]))] for i, n in enumerate(heads["head_names"])} print(slots) # -> {op: nth_weekday, ord: last, dow: wed, scope: year, ...} (compose + materialize in the repo) ``` ## License MIT. Experimental research model — see the [repo](https://github.com/ceritium/sundial) for the full IR grammar, renderers (validated by execution), training/distillation pipeline, and benchmarks.