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:
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):
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 for the full IR grammar, renderers (validated by execution), training/distillation pipeline, and benchmarks.
- Downloads last month
- 38