Graf
Paragraphs will be stacked until morale improves.
A small encoder that recovers paragraph breaks in flattened news text.
| Task | Paragraph break recovery (token classification) |
| Size | ~19 MB weights (trimmed vocab, int8 embeddings, fp16 rest) |
| Runtime | CPU/GPU (PyTorch); browser (ONNX Runtime WASM) |
| Weights | main · web/ for WASM |
| Demo | localangle/graf-demo (in-browser) |
Graf does not rewrite the article. It scores eligible token ends as
BREAK / NO_BREAK, then splits the flattened string at predicted breaks.
Flattened: City council approved a library renovation… Construction starts in March… → Graf inserts breaks between the recovered paragraphs.
Try it
- Live demo: localangle/graf-demo — paste flattened news, paragraphize in the browser.
- Local UI: from the graf repo,
graf-web --web-package checkpoints/graf-web.
Install
pip install "graf @ git+https://github.com/cjdd3b/graf.git"
# or, from a checkout:
pip install -e .
Python ≥ 3.11. Needs torch, transformers, safetensors, huggingface_hub.
Usage
from graf.infer import GrafParagraphizer
graf = GrafParagraphizer("localangle/graf") # thr from graf.json (0.41)
result = graf.paragraphize(
"City council approved a library renovation after public comment. "
"Paulsen and Chen voted against the measure. Construction starts in March."
)
print("\n\n".join(result.paragraphs))
Load a local package folder the same way:
graf = GrafParagraphizer("checkpoints/graf")
Files
| File | Contents |
|---|---|
graf.json |
Format, threshold, kept vocabulary, source metadata |
weights.safetensors |
Trimmed encoder + head (int8 token embeddings, fp16 elsewhere) |
encoder/ |
Ettin/ModernBERT config for the trimmed vocabulary |
tokenizer/ |
Full tokenizer (ids remapped at inference via vocab_map) |
web/ |
Browser package: model.onnx (~17 MB int8), vocab_map.bin, graf-web.json, tokenizer/ |
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
Held-out Extra structure v0.2 test set (publisher-disjoint), package threshold 0.41 (student before compression; packaging keeps break agreement ≈ Jaccard 0.98):
| Metric | Value |
|---|---|
| Boundary F1 | 0.871 |
| Precision / Recall | 0.845 / 0.900 |
| PR-AUC | 0.948 |
| Body–body precision / recall | 0.829 / 0.894 |
| Median |Δ block count| | 2.0 |
How it works
Flattened article text is packed into overlapping 2048-token windows. Eligible
token ends (positions that can host a paragraph boundary) are scored for
BREAK. Probabilities are averaged across windows, thresholded, and used to
reconstruct paragraph blocks.
The published package is a compressed Ettin student (~17M) distilled for on-device / in-browser use.
Limitations
- English news-style prose; other domains are unmeasured.
- Training text / Extra labels are not redistributed here.
- Exact full-article reconstruction is intentionally hard; treat boundary F1 / body–body metrics as the primary quality signal.
- Browser int8 ONNX drifts slightly vs the PyTorch package; the web meta
threshold is nudged for recall (see
web/graf-web.json).
Built on
- Ettin encoder 17M (MIT)
by JHU CLSP. Fine-tuned and compressed for Graf; see
THIRD_PARTY_NOTICES.md.
License
MIT for the Graf code and this package's weights, subject to the Ettin MIT notice above.
Citation
@software{graf_2026,
title = {Graf: Paragraph break recovery for flattened news},
author = {Local Angle},
year = {2026},
url = {https://huggingface.co/localangle/graf},
}
Model tree for localangle/graf
Base model
jhu-clsp/ettin-encoder-17m