Semantic chunker (ModernBERT-base)
Predicts, for each sentence, whether a topic boundary follows it. Intended as a drop-in chunker for RAG pipelines.
What is different here
- 2048-token training context. The closest existing model trains at 1024. Median Wikipedia article is ~980 tokens, so 2048 keeps ~82% of documents in a single window instead of ~53%.
- Supervision at sentence-final tokens only. Labelling every token puts the positive rate near 1% and the model collapses toward never predicting a split. Scoring only sentence-final positions raises it to ~12%.
- Trained on Wikipedia section structure, not book paragraphing.
Results
Both models are ModernBERT-base token taggers sharing a tokenizer, so they are scored at identical positions: P(boundary) read at each sentence's final token. Threshold swept 0.05β0.95; the best operating point is reported.
wiki727k test β in-distribution for this model
| Model | F1 | Precision | Recall | Threshold |
|---|---|---|---|---|
| this model | 0.8156 | 0.8390 | 0.7934 | 0.70 |
mirth/chonky_modernbert_base_1 |
0.4986 | 0.4543 | 0.5524 | 0.90 |
| baseline:every-k (best k, oracle-tuned) (k=2) | 0.1984 | 0.1228 | 0.5159 | β |
| baseline:base-rate random | 0.1177 | 0.1166 | 0.1187 | β |
PubMed-RCT β out-of-distribution for both models
Non-Wikipedia scientific prose with human-assigned section labels. Boundary rate is 35.2% here versus ~11.7% on Wikipedia, so scores are not comparable across the two tables β only across models within one table.
| Model | F1 | Precision | Recall | Threshold |
|---|---|---|---|---|
| this model | 0.6932 | 0.7293 | 0.6605 | 0.55 |
mirth/chonky_modernbert_base_1 |
0.5024 | 0.5095 | 0.4954 | 0.85 |
| baseline:every-k (best k, oracle-tuned) (k=2) | 0.4034 | 0.3500 | 0.4759 | β |
| baseline:base-rate random | 0.3490 | 0.3521 | 0.3460 | β |
Retrieval β does better boundary detection actually help?
Macro-average over 3 corpora (gov_report, qmsum, stackoverflow), embedder BAAI/bge-small-en-v1.5, identical 512-token cap on every strategy so chunk size cannot confound the comparison.
| Strategy | nDCG@10 | R@1 | R@5 | R@10 | mean chunk tokens |
|---|---|---|---|---|---|
fixed-512-ovl64 |
0.8625 | 0.8254 | 0.9218 | 0.9500 | 388 |
chonky-min256 |
0.8615 | 0.7995 | 0.9353 | 0.9635 | 274 |
| ours-min256 (this model) | 0.8583 | 0.7914 | 0.9328 | 0.9659 | 283 |
| ours (this model) | 0.8575 | 0.7854 | 0.9321 | 0.9685 | 215 |
sentence-8 |
0.8536 | 0.7956 | 0.9267 | 0.9536 | 182 |
fixed-512 |
0.8531 | 0.8019 | 0.9279 | 0.9512 | 385 |
recursive-512 |
0.8528 | 0.7903 | 0.9218 | 0.9598 | 308 |
chonky |
0.8498 | 0.7702 | 0.9321 | 0.9684 | 146 |
Per corpus (nDCG@10):
| Strategy | gov_report | qmsum | stackoverflow |
|---|---|---|---|
fixed-512-ovl64 |
0.9423 | 0.6988 | 0.9464 |
chonky-min256 |
0.9285 | 0.7099 | 0.9461 |
| ours-min256 | 0.9225 | 0.7056 | 0.9467 |
| ours | 0.9191 | 0.7254 | 0.9280 |
sentence-8 |
0.9272 | 0.6865 | 0.9472 |
fixed-512 |
0.9348 | 0.6782 | 0.9463 |
recursive-512 |
0.9214 | 0.6910 | 0.9459 |
chonky |
0.9046 | 0.7118 | 0.9329 |
Reading these numbers honestly
baseline:every-k splits every k sentences with k chosen to maximise its own
score β a model-free floor with oracle tuning in its favour. The wiki727k
comparison flatters this model: Wikipedia section boundaries are its training
distribution and are out-of-distribution for chonky, which trained on BookCorpus
paragraphs. The PubMed table is the fair comparison.
Better boundaries did not produce a uniform retrieval win, and the retrieval table above should be read before adopting this. That result is consistent with the published critique of semantic chunking, and it is reported here rather than omitted. Boundary F1 and retrieval quality are different things; this model is much better at the first and situationally better at the second.
When this helps, and when it does not
Use it for long documents whose topic genuinely shifts partway through β transcripts, reports, articles, manuals. That is where fixed-size splitting cuts through the middle of an idea and where boundary detection pays.
Do not use it for corpora whose documents are already shorter than your embedder's window. Splitting a 150-token document into two 75-token fragments makes retrieval worse, not better, no matter how correct the boundary is. On such corpora fixed-size chunking is competitive, faster, and has no dependencies β use that instead.
min_chunk_tokens is a corpus-dependent knob, not a default. Setting it to
256 recovered most of the short-document deficit (+0.019 nDCG@10 on
stackoverflow) but cost almost exactly as much on long transcripts (β0.020 on
qmsum), by merging away the boundaries that made the model useful there. Set it
high for short documents, leave it at 0 for long ones, and measure on your own
corpus rather than trusting either default.
Cost. Chunking with this model is roughly one to two orders of magnitude
slower than a fixed-size splitter (per-strategy wall time is recorded in
retrieval_eval.json). On corpora where it does not win, that cost buys
nothing.
Usage
Nothing to download by hand in any of these β the weights resolve from this repo on first use.
With transformers alone
The custom pipeline is registered in this repo's config, so this needs no
install beyond transformers:
from transformers import pipeline
chunk = pipeline("semantic-chunking", model="0xKitkat/semantic-chunker-modernbert-base",
trust_remote_code=True)
for c in chunk(document_text):
print(c["n_tokens"], c["boundary_score"], c["text"][:80])
Pass return_text=True for plain strings, or any of threshold,
max_chunk_tokens, min_chunk_tokens to tune it.
With the pip package
pip install "boundary-chunker[onnx]" # CPU, no torch
pip install "boundary-chunker[torch]" # GPU
from boundary_chunker import SemanticChunker
chunker = SemanticChunker(backend="onnx") # or backend="torch"
for c in chunker.split(document_text):
print(c.n_tokens, c.text[:80])
There is a CLI too:
boundary-chunk --backend onnx --file report.txt --json
Straight from this repo, no install
hf download 0xKitkat/semantic-chunker-modernbert-base chunk.py --local-dir .
from chunk import SemanticChunker
chunker = SemanticChunker(threshold=0.5, max_chunk_tokens=512)
max_chunk_tokens force-splits over-long chunks at the model's lowest-confidence
interior boundary rather than at an arbitrary offset.
CPU-only, without torch
An ONNX export lives in onnx/ and needs onnxruntime instead of the full
torch stack β roughly 40MB of dependencies rather than a CUDA install. It is
self-contained, with its own config and tokenizer beside the graph, and
backend="onnx" fetches it for you.
The export is verified against the torch model on held-out wiki727k documents:
identical chunk boundaries on 24/24 documents (739β65,468 chars, including
multi-window ones), and max |P(boundary) delta| 8.1e-06 on padded batches. The
pipeline is likewise checked against the direct API β identical boundaries on
12/12 documents β so all three entry points above give the same answer. Both
checks are reproducible with onnx_parity.py in the training repo.
Training
| Base | answerdotai/ModernBERT-base (149M) |
| Data | wiki727k, 582,160 train docs / 30,580,099 sentences |
| Boundary rate | 11.7% |
| Max length | 2048 |
| Effective batch | 32 |
| LR / schedule | 3e-5, linear, 6% warmup |
| Epochs | 1 |
| Loss | class-weighted CE (positive weight 3.0) |
| Hardware | 1x RTX 4070 SUPER (12GB) |
Section titles are dropped from the training text: with headings present the task partly degenerates into "a heading follows", which does not transfer to the unformatted prose a chunker sees in production.
Limitations
- English only.
- Trained on encyclopedic prose; conversational transcripts, code, and tabular documents are out of distribution.
- Boundary F1 is a proxy. If your goal is retrieval quality, measure retrieval.
- Downloads last month
- -
Model tree for 0xKitkat/semantic-chunker-modernbert-base
Base model
answerdotai/ModernBERT-base