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/
Runtime CPU (PyTorch); browser (ONNX Runtime WASM)
Weights v0.3.0 Β· web/ for WASM
Demo 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 β€” paste an article, ask a question, see which paragraphs Cite cites.
  • Local playground: from the cite repo, uv run python deploy/cite_playground.py.

Install

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

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:

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. It is more accurate, especially on broad and abstract questions, and about 3x slower. The 32M build above stays the default.

cite = CitePackage.from_pretrained("localangle/cite", subfolder="68m")

The 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 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 (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

@software{cite_2026,
  title  = {Cite: Which paragraphs answer the question},
  author = {Local Angle},
  year   = {2026},
  url    = {https://huggingface.co/localangle/cite},
}
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for localangle/cite

Quantized
(15)
this model

Spaces using localangle/cite 2