cite / README.md
cjdd3b's picture
Add Cite 68M (68m/ package, web-68m/ browser build), card
24819f9 verified
|
Raw History Blame Contribute Delete
8.78 kB
---
license: mit
language:
- en
library_name: cite
tags:
- cite
- paragraph-citation
- retrieval
- question-answering
- news
- encoder
- modernbert
- on-device
pipeline_tag: text-ranking
base_model: jhu-clsp/ettin-encoder-32m
---
# Cite
Which paragraphs answer the question.
A small encoder that, given a question and ordered paragraph texts, returns a
calibrated probability per paragraph and two thresholds (`needed`,
`contributing`).
| | |
| --- | --- |
| **Task** | Paragraph citation / needed-blocks |
| **Size** | ~42 MB weights (trimmed vocab, int8 embeddings, fp16 rest); a larger 68M build is under [`68m/`](https://huggingface.co/localangle/cite/tree/main/68m) |
| **Runtime** | CPU (PyTorch); browser (ONNX Runtime WASM) |
| **Weights** | [v0.3.0](https://huggingface.co/localangle/cite/tree/v0.3.0) · [`web/`](https://huggingface.co/localangle/cite/tree/main/web) for WASM |
| **Demo** | [localangle/cite-demo](https://huggingface.co/spaces/localangle/cite-demo) (in-browser) |
Cite does **not** write an answer. It ranks the paragraphs you already have and
flags which ones are needed to answer the question.
> Question: *Who voted against the library renovation?*
> → Cite highlights the paragraph naming Paulsen and Chen.
## Try it
- **Live demo:** [localangle/cite-demo](https://huggingface.co/spaces/localangle/cite-demo) — paste an article, ask a question, see which paragraphs Cite cites.
- **Local playground:** from the [cite](https://github.com/localangle/cite) repo, `uv run python deploy/cite_playground.py`.
## Install
```bash
pip install "cite @ git+https://github.com/localangle/cite.git"
# or, from a checkout:
uv sync
```
Python ≥ 3.12. Needs `torch`, `transformers`, `safetensors`, `huggingface_hub`.
## Usage
```python
from cite import CitePackage
cite = CitePackage.from_pretrained("localangle/cite")
paragraphs = [
"The council approved a $4.2 million library renovation.",
"Paulsen and Chen voted against the measure.",
"Construction starts in March.",
]
scores = cite.predict("Who voted against the renovation?", paragraphs)
for score in scores:
if score.needed:
print(score.index, score.probability, paragraphs[score.index])
```
`score.needed` uses the packaged threshold (0.76). `score.contributing` is a
looser relatedness cut (0.17). Logits are Platt-scaled to probabilities at load
time; you do not fit thresholds yourself for the published package.
Load a local folder the same way:
```python
cite = CitePackage.load("path/to/package") # or from_pretrained("./path")
```
## Files
| File | Contents |
| --- | --- |
| `cite.json` | Format, Platt scaling, thresholds, source metadata |
| `weights.safetensors` | Trimmed encoder + head (int8 token embeddings, fp16 elsewhere) |
| `encoder/` | ModernBERT/Ettin config for the trimmed vocabulary |
| `tokenizer/` | Tokenizer with `[MASK]` as the block marker |
| `web/` | Browser package: `model.onnx` (~36 MB int8), `vocab_map.bin`, `cite-web.json`, `tokenizer/` |
| `68m/` | The 68M build as a full Python package (same layout as the repo root; ~107 MB weights) |
| `web-68m/` | Browser package for the 68M build (`model.onnx` ~88 MB int8) |
PyTorch weights expand to fp32 at load; only storage is compressed. The `web/`
ONNX graph is what the static demo runs in WASM (soft cap 2048 tokens).
## Accuracy
On a held-out real-news needed-blocks eval (v59, 1,800 questions over local
news outlets; threshold chosen on a separate calibration split):
| Metric | Cite-32M package |
| --- | --- |
| Coverage | 0.692 |
| Exact set match | 0.725 |
| Empty-set accuracy | 0.897 |
| Any-extra rate | 0.066 |
The browser ONNX build on the same eval: exact 0.722, coverage 0.687, empty 0.902.
### Broad and abstract questions
The eval above uses specific fact-lookup questions. Later versions add training so that broad
questions ("How will the library change as a result of the renovations?") score the paragraph
that answers them instead of washing out.
- **v0.2.0** fine-tuned on LLM-written broad rewrites of real training questions.
- **v0.3.0** adds synthetic short articles where nearly every paragraph shares the topic word,
with hard negatives, then averages the weights 0.75/0.25 with v0.2.0 to keep the specific-question
operating point.
On a 117-item hand-written probe (specific question vs. an abstract rewrite of it; 92 items after
removing 25 where several paragraphs plausibly answer the rewrite), abstract-question results:
| Abstract question | v0.1.0 | v0.2.0 | v0.3.0 |
| --- | --- | --- | --- |
| Top-1 (all 117) | 0.650 | 0.744 | 0.752 |
| Needed recall (all 117) | 0.402 | 0.752 | 0.761 |
| Top-1 (92 audited) | 0.826 | 0.848 | 0.859 |
| Top-3 (92 audited) | n/a | 0.957 | 0.978 |
Packaged-model numbers for v0.2.0 and v0.3.0; v0.1.0 from the fp32 student. Specific-question probe
top-1 is 0.949 (v0.2.0) and 0.966 (v0.3.0). The probe is small and written by one author;
treat it as a regression check, not a benchmark.
On a short, topic-saturated example article, v0.3.0 improves but does not solve the hardest
case: for "What specific improvements will be made...?" the improvements paragraph moves from
rank 4 (18%) to rank 2 (60%), still behind a funding paragraph that repeats the word
"renovation". See `experiments/107-cite-abstract-probe`, `experiments/108-cite-broad-consistency`
and `experiments/109-cite-hard-negatives`.
## Cite 68M
A larger build, distilled from the same teacher with the same data and training steps, on
[Ettin-68M](https://huggingface.co/jhu-clsp/ettin-encoder-68m). It is more accurate, especially on broad
and abstract questions, and about 3x slower. The 32M build above stays the default.
```python
cite = CitePackage.from_pretrained("localangle/cite", subfolder="68m")
```
The [demo](https://huggingface.co/spaces/localangle/cite-demo) has a model switch; the 68M build downloads
about 88 MB on first use and is cached after that. Packaged thresholds: needed 0.79, contributing 0.17.
| | Cite 32M (v0.3.0) | Cite 68M |
| --- | --- | --- |
| Weights (package) | 41.8 MB | 107.0 MB |
| Browser `model.onnx` | 36.3 MB | 88.3 MB |
| v59 eval: coverage / exact / empty-set / any-extra rate | 0.692 / 0.725 / 0.897 / 0.066 | 0.729 / 0.763 / 0.923 / 0.042 |
| Abstract probe, 92 audited items: top-1 / needed recall | 0.859 / 0.859 | 0.902 / 0.891 |
| Abstract probe, all 117: top-1 | 0.752 | 0.786 |
| Browser (WASM, 1 thread): 7 paragraphs / 44 paragraphs | 185 ms / 1.4 s | 547 ms / 4.2 s |
On the short example article, for "What specific improvements will be made to the library as a result of the
renovation?", the improvements paragraph scores 93.5% on 68M (60% on 32M, behind a funding paragraph).
The same caveats apply: one run per size, a small hand-written probe, and browser timings from a single
machine. See `experiments/110-cite-68m`.
## Latency
Batch-1, CPU, AMD EPYC (HF `cpu-upgrade`, 8 vCPU) for the related ONNX int8
form: about 44 ms p50 at 5–12 paragraphs and 268 ms at 33–60. The portable
PyTorch package this repo ships is the accuracy-matched serving artifact; long
documents are dominated by attention cost.
## How it works
Cite is a fine-tuned [Ettin](https://huggingface.co/jhu-clsp/ettin-encoder-32m)
encoder (ModernBERT family). The question and each paragraph are packed into
one sequence with a `[MASK]` marker before every paragraph; the head reads the
marker states. A 400M teacher labeled real news; the 32M student was distilled
then fine-tuned on LLM-written broad rewrites of its training questions
(consistency distillation) and on synthetic hard-negative articles, and compressed (trimmed vocabulary, int8
embeddings, fp16 weights).
## Limitations
- Cite ranks paragraphs; it does not generate answers or quote spans inside a
paragraph.
- Lexical overlap still matters: "big 10" and "Big Ten" can score differently.
Scores for a paragraph also drift down in very long articles.
- Eval is English news-style text. Other domains are unmeasured.
- Empty-set accuracy drops a few points from calibration to eval; treat the
packaged threshold as a starting point for a new domain.
- Documents longer than `max_tokens` (7999) drop trailing paragraphs.
- Training used private teacher labels and news text that is not redistributed
here.
## Built on
- [Ettin encoder 32M](https://huggingface.co/jhu-clsp/ettin-encoder-32m) (MIT)
by JHU CLSP. Fine-tuned and compressed for Cite; see
`THIRD_PARTY_NOTICES.md`.
## License
MIT for the Cite code and this package's weights, subject to the Ettin MIT
notice above.
## Citation
```bibtex
@software{cite_2026,
title = {Cite: Which paragraphs answer the question},
author = {Local Angle},
year = {2026},
url = {https://huggingface.co/localangle/cite},
}
```