File size: 4,417 Bytes
1c0079f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
---
license: mit
language:
- en
library_name: graf
tags:
- graf
- paragraph-segmentation
- token-classification
- news
- encoder
- modernbert
- on-device
pipeline_tag: token-classification
base_model: jhu-clsp/ettin-encoder-17m
metrics:
- f1
---

# 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`](https://huggingface.co/localangle/graf/tree/main) · [`web/`](https://huggingface.co/localangle/graf/tree/main/web) for WASM |
| **Demo** | [localangle/graf-demo](https://huggingface.co/spaces/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](https://huggingface.co/spaces/localangle/graf-demo) — paste flattened news, paragraphize in the browser.
- **Local UI:** from the [graf](https://github.com/cjdd3b/graf) repo, `graf-web --web-package checkpoints/graf-web`.

## Install

```bash
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

```python
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:

```python
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](https://huggingface.co/jhu-clsp/ettin-encoder-17m)
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](https://huggingface.co/jhu-clsp/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

```bibtex
@software{graf_2026,
  title  = {Graf: Paragraph break recovery for flattened news},
  author = {Local Angle},
  year   = {2026},
  url    = {https://huggingface.co/localangle/graf},
}
```