ceritium's picture
Model card: link to GitHub + demo, usage, benchmarks
56a8677 verified
|
Raw
History Blame Contribute Delete
4.03 kB
---
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.