Qwen3.5-2B distilled for math-content extraction from web pages

Give it the raw text of a web page; it returns just the mathematical content, verbatim. Trained on 197,269 (page → extraction) pairs produced by GPT-5.6 on UltraData-Math L2-preview — the full udm_l2preview_gpt56_extract_200k train split.

This is a student of GPT-5.6, not an oracle. Every number below measures agreement with the teacher's extraction on held-out pages. It does not measure whether the teacher was right.

Measured on 498 held-out pages

value
charF1 vs teacher 0.9453
LCS vs teacher 0.9423
strict order-preservation 97.14%
contiguous-span coverage (mean) 0.9801
pages with coverage < 0.9 6.02%
length-ratio error 0.0620
formula-token excess vs source 10.64%
boilerplate residue 3.21%
empty-output rate 0.00%
20-gram repeat rate (mean) 0.0078
pages with repeat rate > 0.3 0.8%

The held-out set is a 498-page stratified slice of the dataset's own heldout split (page length × formula density), content-disjoint from training.

More data would not have helped — we checked

Train rows 5,002 19,999 100,000 197,269 (this model)
charF1 0.9116 0.9325 0.9447 0.9453

Going from 100,000 to 197,269 moved charF1 by +0.0007, 95% CI [−0.0037, +0.0054] (paired bootstrap, same 498 pages; 97 pages better, 89 worse, 312 identical). Fitting the four points to y = C − A·n^(−b) gives a ceiling of 0.9512 with max residual 0.0010 — inside the 0.0054 evaluation standard error. From here, another +0.005 would take ≈4.6M pairs, 23× the training set.

Practical consequence: the 100,000-row checkpoint is statistically indistinguishable from this one (it is very slightly better on contiguous coverage, very slightly worse on charF1, neither significant). If you are reproducing this, train on 100,000 and save half the compute. We released the full-pool model because it is the endpoint of the curve, not because it is better.

Usage

The prompt is not optional — the model was trained with this exact instruction as a prefix, and it is shipped as extract_prompt.txt.

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

M = "path/to/this/model"
tk = AutoTokenizer.from_pretrained(M)
model = AutoModelForCausalLM.from_pretrained(M, dtype=torch.bfloat16, device_map="cuda")
PROMPT = open(f"{M}/extract_prompt.txt").read().rstrip()

def extract(page_text: str, max_new_tokens: int = 6144) -> str:
    ids = tk(PROMPT + "\n\n" + page_text + "\n\n", return_tensors="pt").to(model.device)
    out = model.generate(**ids, max_new_tokens=max_new_tokens, do_sample=False)
    return tk.decode(out[0][ids["input_ids"].shape[1]:], skip_special_tokens=True)

Greedy decoding. Our evaluation used max_new_tokens=6144; shorter caps silently truncate the long pages, which are the ones that matter.

Training recipe

Base Qwen3.5-2B-Base. One epoch, lr 1e-5, per-device batch 1 × grad-accum 16 (effective batch 16), max sequence length 12,288, bf16, SDPA attention, gradient checkpointing on, warmup ratio 0.03, cosine schedule, weight decay 0, grad-norm clip 1.0, seed 7. Loss is masked to the output span only — the page is context the student is given, not something it should learn to reproduce, or it could score well by echoing input. Sequences over the limit are truncated from the input side: losing the page tail beats losing the target being scored.

1,724 minutes on one A100-80GB. Every rung of the scaling curve above used this recipe byte-for-byte; the only variable was the number of training rows.

Limits — read before running it over a corpus

  • formula-token excess 10.64% is not 10.64% hallucination. The criterion counts any excess of one token, and the prompt itself mandates notation repair. The tail worth watching is excess > 10 tokens. Measure repetition directly (we use a 20-gram repeat rate) rather than trusting the token count as a proxy.
  • 0.8% of pages come back with a 20-gram repeat rate above 0.3 — that is a generation loop, and at corpus scale it is thousands of pages. Filter on it; do not assume the empty-output rate of 0.00% means nothing goes wrong.
  • Empty output is a valid answer. The prompt instructs the model to emit nothing when a page has no substantive math. The 0.00% empty rate above is on 498 math-bearing pages and says nothing about behaviour on non-math pages.
  • The model has never seen a page longer than 20,000 characters. This is the sharpest limitation and it is not obvious from the training recipe. The corpus this model was distilled on was normalised with an upper cut that dropped pages over 20,000 chars rather than truncating them, so the longest training page is 19,997 chars (measured over all 197,269 pairs). The dataset's length stratification has a >12K bucket holding 19.2% of the data — but its real range is 12K–20K, because stratification ran after that cut. Measured against a realistic target corpus (TeraflopAI/udml2-labeled): 28.1% of pages are longer than the longest page in training by characters, 5.5% by tokens, up to 49,953 chars / 24,961 tokens. On that band the model is out of distribution and we have not measured how it behaves. Do not truncate to compensate — the context window is 262,144 and the whole page fits; truncation guarantees loss where full input merely risks it. Measure output quality on long pages before trusting them.
  • Trained at 12,288 tokens. max_position_embeddings in the config is 262,144 because the base model allows it. 1,692 training pairs (0.86%) had their input clipped by this limit, losing 2.95M input tokens in total; one pair had its target clipped. Behaviour far beyond the training length is untested.
  • Faithfulness is measured against the teacher, by construction. In the parent dataset, 99.722% of generated characters appear verbatim in the source page and net altered text is 0.1733% — but that is measured on the teacher's output. This student was not audited against source pages directly.
  • English web pages of mathematical content. Other languages and other domains are untested.

Files

File MD5
model.safetensors 74a23296647d219952d18d6ebe31a6ea
tokenizer.json 4010c9c068169f29bca5d89c5d8e967c
config.json d6c1c2df441ab7e4968d1b70c86db3a2
extract_prompt.txt 5fb78ad1d48cc54fb94db3304a1afbe4
Downloads last month
-
Safetensors
Model size
2B params
Tensor type
BF16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for oklenAI/udm_doc_extract_qwen3.5_2B

Finetuned
(67)
this model