schemer / README.md
pveugen's picture
Harness format-gate fix: Schemer 0.800 / int4 0.793 on the pooled eval
a580ca0 verified
|
Raw
History Blame Contribute Delete
12.1 kB
---
license: other
license_name: desert-ant-labs-source-available-1.0
license_link: https://license.desertant.ai/1.0
language:
- multilingual
- en
- de
- es
- fr
- it
- nl
- da
- 'no'
- sv
- pl
- pt
- ja
- zh
tags:
- text
- structured-output
- schema
- json
- extraction
- multilingual
- on-device
- core-ml
- onnx
pipeline_tag: other
---
# Schemer, on-device text to structured JSON
Schemer is a **schema-native extraction model**: it takes free text and a
developer-supplied schema, and returns a JSON object that matches the
schema. The schema is the model's input, not a prompt suggestion, and every
field is decoded by a head built for its type. It does not generate text.
- **211M parameters**: 218 MB int8 (verified lossless) or 111 MB int4
- **13 languages**, 0.075 per-language accuracy spread
- **Nested schemas**: arrays of objects extract via deterministic
segment-and-recurse in the harness (0.99 on the internal nested eval)
- **Documents up to ~1,100 tokens** (roughly 3 pages); short inputs run a
fast 256-token path
- **Offline**, ~200 MB RAM; 8.7 ms per encoder pass on an Apple Neural
Engine
Three guarantees no generative extractor offers:
1. **Typed by construction.** Labels are always one of your declared
choices, numbers arrive clamped to your range, datetimes are ISO 8601,
and relative expressions ("tomorrow at 3pm", "i morgen kl 15",
misspellings included) resolve against device time.
2. **Absence detection.** Fields the text does not state come back null.
Schemer scores 0.91 on this; every LLM and span extractor we benched,
at any size, scores between 0.18 and 0.43.
3. **Verbatim with offsets.** Extracted strings are substrings of the
input; the SDK surfaces character offsets, so hallucination auditing is
a substring check.
> **Private pre-release.** Weights are staged here ahead of the SDK
> release.
## Try it
- **iOS / macOS:** Swift SDK coming soon. The int4
Core ML encoders in `coreml/` are ANE-validated (8.1 ms/forward at the
256-token shape) and eval-confirmed on-device.
- **Android / web:** ONNX graphs are staged in `onnx/` (int4 web encoder +
fp32 support graphs, quality measured through the actual graphs); the JS
harness port ships with the SDK.
- The deterministic harness (relative dates, duration units, format gates,
string trims, anchor injection) ships as data in `harness/*.json`; every
SDK port must pass the bundled conformance fixtures bit-for-bit.
## Files
| File | Format | Size | Use |
|---|---|---:|---|
| `model-int8.pt` | int8 per-row | 218 MB | start here; full quality (0.800 overall) |
| `model-int4-awq.pt` | int4 AWQ | 111 MB | when download or RAM is tight (0.793 overall) |
| `model-fp32.pt` | fp32 | 850 MB | converting to other formats; not for deployment |
| `onnx/encoder_web4e4.onnx` | int4 ONNX | 111 MB | run in the browser or on Android |
| `onnx/*.onnx` | fp32 ONNX | 80 MB | decoding graphs; always ship with the encoder |
| `coreml/*.mlpackage.zip` | int4 Core ML | 84 MB each | run on iOS / macOS; 256 + 1216 token shapes, ANE-resident, eval-confirmed on-device |
| `tokenizer/`, `keep_ids.json` | | 11 MB | required by every runtime |
| `harness/*.json` | | <1 MB | post-processing and nested-recursion rules every SDK port must implement |
| `manifest.json` | | | verify downloads; measured quality per artifact |
## Use
The schema is plain JSON (paste an OpenAI/Gemini `responseSchema` and it
works). One call per document:
```json
{
"title": {"type": "string", "describe": "the task title", "max_chars": 60},
"guest": {"type": "string", "describe": "person the meeting is with", "nullable": true},
"start": {"type": "datetime","describe": "start time", "nullable": true},
"duration_min": {"type": "number", "describe": "duration in minutes", "min": 0, "max": 480, "nullable": true},
"is_recurring": {"type": "boolean", "describe": "is this a recurring task"}
}
```
Input `"Meeting with Sarah tomorow at 3pm for an hour to review the Q3
deck. Recurring weekly."` returns (with device time 2026-07-05):
```json
{"title": "review the Q3 deck", "guest": "Sarah",
"start": "2026-07-06T15:00", "duration_min": 60, "is_recurring": true}
```
Note the typo tolerance, the relative-date resolution, the unit conversion
("an hour" to 60), and the factoring: the title is the purpose of the
meeting, the person lands in `guest`, and the temporal clutter lands in
the temporal fields.
## Model
- **Encoder:** pruned mmBERT-base (22 layers, 103k-token vocabulary kept of
256k), jointly encoding `[schema summary ||| document]`. Two compiled
input shapes: 256 tokens (records) and 1216 tokens (documents). The
1216-token shape is the input limit: schema summary + document together;
longer inputs are truncated, so budget roughly 1,100 tokens of document.
- **Reader:** one shared cross-attention reader over encoder states.
- **Heads:** thin per-type decoders: label entailment, 3-way boolean
(absent/false/true), BIO span tagging for strings and arrays with trained
presence gates, datetime and number component decoders.
- **Harness:** deterministic post-processing (locale number parsing, ISO
composition, 13-language relative-date lexicons, format gates). Shipped
as data plus a reference implementation; ports are conformance-tested.
## Evaluation
9,021 held-out records across seven usage slices, 13 languages, schemas
the model never saw, one order-insensitive, absence-aware scorer for
every model. The overall score weights the slices by product usage:
short records 0.30, long documents 0.15, long documents with same-type
distractors 0.15, reviews and registers 0.15, calendar 0.10, factored
domains 0.10, nested schemas 0.05. Every model ran in its documented
configuration (tool calling for FunctionGemma, template prompts for
NuExtract-2.0, JSON chat for the instruct models, in-process span
extraction for GLiNER2); sizes are measured bytes of the artifact
benched.
| Model | Overall | Absence (boolean) | Params | On disk |
|---|---:|---:|---|---|
| Gemma 4 26B A4B (int4 AWQ) | 0.834 | 0.428 | 26.6B | 17.2 GB |
| Claude Haiku 4.5 (API) | 0.816 | 0.431 | n/a | API only |
| Qwen 3.5 9B | 0.812 | 0.434 | 9.1B | 9.1 GB |
| **Schemer** | **0.800** | **0.911** | 211M | 218 MB int8 |
| **Schemer (int4)** | **0.793** | 0.908 | 211M | 111 MB int4 |
| Ministral 3 8B | 0.790 | 0.430 | 8.0B | 17.8 GB |
| Gemma 4 E2B (official int4 QAT) | 0.780 | 0.427 | 5.1B | 8.3 GB |
| Qwen 3.5 0.8B | 0.651 | 0.351 | 0.87B | 1.75 GB |
| NuExtract-2.0-2B | 0.643 | 0.393 | 2.2B | 4.4 GB |
| GLiNER2-multi | 0.585 | 0.374 | 307M | 309 MB int8* |
| GLiNER2-base | 0.524 | 0.368 | 205M | 834 MB |
| NuExtract-tiny-v1.5 | 0.334 | 0.222 | 0.5B | 954 MB |
| FunctionGemma 270M (tool calling) | 0.288 | 0.181 | 0.27B | 0.54 GB |
*GLiNER2-multi int8 size verified by us with the same
quantize-and-rebench methodology as our own int8 claim (accuracy held
within 0.004 of fp32).
### Per slice
| Model | Short | Long doc | Long + distractors | Register | Calendar | Factored | Nested |
|---|---:|---:|---:|---:|---:|---:|---:|
| Gemma 4 26B A4B | **0.844** | **0.844** | 0.693 | 0.815 | 0.829 | 0.954 | **1.000** |
| Claude Haiku 4.5 | 0.838 | 0.759 | **0.762** | **0.831** | 0.802 | 0.817 | **1.000** |
| Qwen 3.5 9B | 0.828 | 0.807 | 0.717 | 0.791 | 0.774 | 0.891 | **1.000** |
| **Schemer** | 0.759 | 0.708 | 0.685 | 0.829 | **0.946** | 0.946 | 0.988 |
| **Schemer (int4)** | 0.754 | 0.689 | 0.671 | 0.820 | 0.943 | **0.967** | 0.985 |
| Ministral 3 8B | 0.818 | 0.811 | 0.711 | 0.720 | 0.796 | 0.790 | 0.998 |
| Gemma 4 E2B | 0.809 | 0.785 | 0.705 | 0.773 | 0.719 | 0.759 | **1.000** |
| Qwen 3.5 0.8B | 0.721 | 0.635 | 0.560 | 0.581 | 0.529 | 0.689 | 0.936 |
| NuExtract-2.0-2B | 0.763 | 0.704 | 0.542 | 0.648 | 0.496 | 0.805 | 0.000† |
| GLiNER2-multi | 0.683 | 0.650 | 0.388 | 0.662 | 0.468 | 0.783 | 0.000† |
| GLiNER2-base | 0.619 | 0.587 | 0.371 | 0.612 | 0.362 | 0.668 | 0.000† |
| NuExtract-tiny-v1.5 | 0.406 | 0.277 | 0.248 | 0.280 | 0.275 | 0.417 | 0.439 |
| FunctionGemma 270M | 0.339 | 0.254 | 0.232 | 0.274 | 0.299 | 0.421 | 0.000† |
†The NuExtract-2.0, GLiNER2, and FunctionGemma interfaces cannot express
arrays of objects; their nested cells score the resulting empty output.
This is an interface limit, not an extraction failure.
### Per field type
Bold marks the best score in each row. Schemer's architecture shows plainly: it owns the typed/absence rows and trails larger extraction LLMs on free-text rows.
| Type | Schemer | Schemer (int4) | NuExtract-2.0-2B (2.2B) | Qwen 3.5 0.8B |
|---|---:|---:|---:|---:|
| boolean | **0.911** | 0.908 | 0.393 | 0.351 |
| datetime | **0.769** | 0.752 | 0.641 | 0.652 |
| array | **0.766** | 0.750 | 0.731 | 0.686 |
| number | **0.750** | 0.743 | 0.670 | 0.679 |
| string | 0.648 | 0.637 | **0.774** | 0.716 |
| label | 0.629 | 0.609 | **0.636** | 0.616 |
### Per language
Measured on the three slices that cover all 13 languages with identical
composition (short records, long documents, long documents with
distractors; ~600 records per language).
| Language | Schemer | Qwen 3.5 0.8B | GLiNER2-multi |
|---|---:|---:|---:|
| French | **0.745** | 0.649 | 0.606 |
| Spanish | **0.734** | 0.662 | 0.610 |
| Italian | **0.731** | 0.653 | 0.603 |
| Portuguese | **0.729** | 0.640 | 0.601 |
| German | **0.728** | 0.635 | 0.591 |
| Dutch | **0.724** | 0.621 | 0.600 |
| Danish | **0.723** | 0.618 | 0.594 |
| English | **0.717** | 0.706 | 0.632 |
| Norwegian | **0.717** | 0.620 | 0.600 |
| Swedish | **0.715** | 0.633 | 0.624 |
| Japanese | **0.707** | 0.614 | 0.384 |
| Chinese | **0.683** | 0.640 | 0.408 |
| Polish | **0.670** | 0.609 | 0.597 |
Schemer scores 0.800 overall; every model above it runs 9.1B to 26.6B
parameters or an API. NuExtract-2.0-2B, the closest task-specific
extractor, scores 0.643 at ten times the parameters. Schemer wins all
13 languages against both the strongest sub-1B LLM and GLiNER2-multi,
posts the top calendar score of any model at any size (0.946 next to
the 26B's 0.829), and is the only model with reliable absence detection
(0.911 next to a 0.18 to 0.43 field). Long documents: 0.708 at ~1,100
tokens; 0.685 when the document contains same-type distractor records.
## Limitations
- **Nested schemas** extract via the harness (segment and recurse), not
the model itself: quality on messy real-world prose is not yet benched
externally; the 0.99 figure is our internal template-based eval.
- **Extractive strings only:** Schemer will not compose or rewrite text;
abstractive titles from messy prose trail extraction LLMs (string
0.641 vs 0.774 for NuExtract-2.0-2B and ~0.86 for the 8B+ class). It
does factor titles extractively (purpose clause, people and times split
into their fields), but the title is always a substring of the input.
- **Judgment labels** requiring world knowledge (severity triage) trail
the 8B+ class (0.629 vs ~0.82).
- **Not a NER model:** it fills your fields; it does not enumerate every
entity mention (CrossNER span-F1 0.24 vs GLiNER2's 0.59).
- English is the weakest of the 13 languages relative to competitors.
## Citation
```bibtex
@software{schemer_2026,
title = {Schemer: on-device text to structured JSON},
author = {Desert Ant Labs},
year = {2026},
url = {https://huggingface.co/desert-ant-labs/schemer}
}
```
## License
**[Desert Ant Labs Source-Available License, Version 1.0](https://license.desertant.ai/1.0)**.
Free below **100,000 monthly active devices per platform, for each model**;
above that a commercial license is required (<licensing@desertant.ai>). You
may embed the model in your application; you may not use it, its outputs,
or its logs to train a competing on-device model. Credit Desert Ant Labs in
your app: <https://license.desertant.ai/attribution>.
Third-party upstream components retain their original licenses; see
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).
---
© 2026 Desert Ant Labs · <https://desertant.ai>