| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>Document Classification Leaderboard</title> |
| <style> |
| :root{ |
| color-scheme:light dark; |
| --paper:#eef1f4; --surface:#ffffff; --ink:#17222c; --muted:#5c6b78; |
| --hair:#d7dee4; --hair-strong:#c2ccd4; |
| --accent:#1d5fa8; --accent-soft:#e5eefa; |
| --good:#2f8f6f; --good-soft:#dcefe8; --bad:#c0473b; --warn:#c98a2b; |
| --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif; |
| --sans:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; |
| --mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace; |
| } |
| @media (prefers-color-scheme:dark){ |
| :root{--paper:#12171c;--surface:#1a2229;--ink:#e6ecf1;--muted:#93a2ae; |
| --hair:#2b353e;--hair-strong:#38444e;--accent:#6aa9e6;--accent-soft:#1e2b3a; |
| --good:#57b895;--good-soft:#173029;--bad:#e0796d;--warn:#d9a856;} |
| } |
| *{box-sizing:border-box} |
| body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);line-height:1.6;font-size:16px;-webkit-font-smoothing:antialiased} |
| .wrap{max-width:1180px;margin:0 auto;padding:clamp(20px,5vw,60px) clamp(18px,6vw,64px)} |
| .eyebrow{font-family:var(--mono);font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--accent);font-weight:600;margin:0 0 14px} |
| h1{font-family:var(--serif);font-weight:600;font-size:clamp(28px,5vw,44px);line-height:1.08;letter-spacing:-.01em;margin:0 0 14px} |
| .lede{font-size:18px;color:var(--muted);max-width:82ch;margin:0 0 8px} |
| a{color:var(--accent)} |
| code{font-family:var(--mono);font-size:.86em;background:var(--accent-soft);padding:.1em .38em;border-radius:4px} |
| .tracknote{background:var(--surface);border:1px solid var(--hair);border-radius:12px;padding:16px 20px;margin:26px 0 6px;font-size:14px} |
| .tracknote b{font-family:var(--mono);font-size:12px;letter-spacing:.04em;color:var(--accent)} |
| .tracknote ul{margin:8px 0 0;padding-left:18px}.tracknote li{margin:4px 0} |
| .tablebox{overflow-x:auto;margin:14px 0} |
| table{border-collapse:collapse;width:100%;font-size:14px;min-width:860px} |
| th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--hair)} |
| th{font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);font-weight:600} |
| td.n,th.n{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--mono)} |
| tbody tr:last-child td{border-bottom:none} |
| tr.ours{background:var(--accent-soft)} |
| .rank{font-family:var(--mono);color:var(--muted);width:2ch} |
| .name{font-weight:650} |
| .chip{display:inline-block;font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.03em;padding:2px 7px;border-radius:20px;white-space:nowrap;margin-left:6px} |
| .c-com{background:var(--accent-soft);color:var(--accent)} |
| .c-open{background:var(--good-soft);color:var(--good)} |
| .c-cloud{background:#f0e9dc;color:var(--warn)} |
| @media (prefers-color-scheme:dark){.c-cloud{background:#2a2312;color:var(--warn)}} |
| .c-gen{background:var(--hair);color:var(--muted)} |
| .tabs{display:flex;gap:8px;margin:20px 0 4px} |
| .tab{font-family:var(--mono);font-size:12px;font-weight:600;letter-spacing:.03em;padding:7px 14px;border-radius:8px; |
| border:1px solid var(--hair-strong);background:var(--surface);color:var(--muted);cursor:pointer} |
| .tab.on{background:var(--accent);color:#fff;border-color:var(--accent)} |
| .oursonly{color:var(--muted)} |
| .bar{position:relative;height:15px;background:var(--hair);border-radius:4px;min-width:110px} |
| .bar>span{position:absolute;left:0;top:0;bottom:0;background:var(--accent);border-radius:4px} |
| .bar.hl>span{background:var(--good)} |
| .barnum{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:12.5px;margin-left:8px;color:var(--muted)} |
| .barcell{display:flex;align-items:center;min-width:200px} |
| footer{margin-top:52px;border-top:1px solid var(--hair-strong);padding-top:16px;color:var(--muted);font-size:12.5px;font-family:var(--mono)} |
| footer img{vertical-align:middle;margin-right:10px;border-radius:6px} |
| .muted{color:var(--muted)} |
| </style> |
| </head> |
| <body> |
| <div class="wrap"> |
| <p class="eyebrow">Open-vocab document classification · benchmark</p> |
| <h1>Document Classification Leaderboard</h1> |
| <p class="lede">Zero-shot document-type classification — labels supplied at inference, not baked into a |
| head. Ranked by <strong>doc-type mean macro-F1</strong> across DocLayNet, Forms and Tobacco on the |
| held-out |
| <a href="https://huggingface.co/datasets/nutrientdocs/document-classification-benchmark">document-classification-benchmark</a>. |
| Nutrient's in-house small models vs. cloud frontier VLMs, all scored by the same |
| <code>score_classify.py</code> (unweighted macro-F1, argmax over candidate labels).</p> |
|
|
| <div class="tracknote"> |
| <b>TRACKS · macro-F1</b> |
| <ul> |
| <li><strong>DocLayNet / Forms / Tobacco</strong> — document-type tracks (n=377 / 360 / 500); the |
| headline <strong>doc-type mean</strong> averages these three and rows are ranked by it.</li> |
| <li><strong>OOD</strong> (unseen types) & <strong>OOV</strong> (synonym label wording) — robustness |
| tracks, reported for the <em>open-vocab</em> models (Nutrient + the non-VLM generalists). Cloud VLMs |
| train on ~all data, so held-out/open-vocab generalization isn't a meaningful axis for them (shown —).</li> |
| </ul> |
| <p style="margin:10px 0 0"><strong>Reading it:</strong> the in-house models match/lead the cloud on the |
| <em>visual</em> tracks (DocLayNet, Forms) at zero API cost, and trail on <em>Tobacco</em> — a |
| read-the-header task where a VLM's text reading wins. A dash (—) = track not run for that model.</p> |
| </div> |
|
|
| <div class="tabs"> |
| <button id="t-nonvlm" class="tab on" onclick="setTab('nonvlm')">vs open-vocab models (non-VLM)</button> |
| <button id="t-vlm" class="tab" onclick="setTab('vlm')">vs cloud VLMs</button> |
| </div> |
| <div id="banner"></div> |
| <div class="tablebox"><table><thead id="head"></thead><tbody id="rows"></tbody></table></div> |
| <p class="muted" style="font-size:13px"> |
| <span class="chip c-com">Nutrient · commercial</span> |
| <span class="chip c-open">Nutrient · open-weight</span> |
| <span class="chip c-cloud">cloud VLM</span> |
| Highlighted rows are Nutrient models. Numbers are provisional pending a clean scoring pass.</p> |
|
|
| |
| <p id="models-benchmarked" class="muted" style="font-size:12.5px;margin-top:22px"> |
| <strong>Models benchmarked:</strong> |
| <a href="https://huggingface.co/nutrientdocs/document-classification-v2">nutrientdocs/document-classification-v2</a> · |
| <a href="https://huggingface.co/nutrientdocs/document-classification-v1">nutrientdocs/document-classification-v1</a> · |
| <a href="https://huggingface.co/google/siglip2-so400m-patch16-512">google/siglip2-so400m-patch16-512</a> · |
| <a href="https://huggingface.co/google/siglip2-base-patch16-512">google/siglip2-base-patch16-512</a> · |
| <a href="https://huggingface.co/google/siglip-base-patch16-224">google/siglip-base-patch16-224</a> · |
| <a href="https://huggingface.co/openai/clip-vit-large-patch14-336">openai/clip-vit-large-patch14-336</a> · |
| <a href="https://huggingface.co/openai/clip-vit-base-patch32">openai/clip-vit-base-patch32</a> · |
| <a href="https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K">laion/CLIP-ViT-H-14</a> · |
| <a href="https://huggingface.co/apple/DFN5B-CLIP-ViT-H-14-378">apple/DFN5B-CLIP-ViT-H-14-378</a> · |
| <a href="https://huggingface.co/timm/eva02_large_patch14_clip_336.merged2b_s6b_b61k">timm/eva02_large_patch14_clip_336</a>. |
| Cloud VLMs (no public HF page): GPT-4o / 4o-mini / 4.1 / 4.1-mini · Gemini 2.5 Pro / Flash / Flash-Lite · |
| Claude Opus / Sonnet / Haiku.</p> |
|
|
| <footer> |
| <a href="https://nutrient.io/"><img src="https://avatars2.githubusercontent.com/u/1527679?v=3&s=200" height="34" /></a> |
| Macro-F1 on nutrientdocs/document-classification-benchmark · same <code>score_classify.py</code> for all · |
| maintained and funded by <a href="https://nutrient.io/">Nutrient</a> · |
| flagship: <a href="https://huggingface.co/nutrientdocs/document-classification-v2">document-classification-v2</a> |
| </footer> |
| </div> |
|
|
| <script> |
| let DATA=[]; |
| let TAB="nonvlm"; |
| function setTab(t){TAB=t; |
| document.getElementById('t-nonvlm').className='tab'+(t==='nonvlm'?' on':''); |
| document.getElementById('t-vlm').className='tab'+(t==='vlm'?' on':''); |
| render();} |
| const CORE=[["doclaynet","DocLayNet"],["forms","Forms"],["tobacco","Tobacco"]]; |
| const EXTRA=[["ood","OOD*"],["oov","OOV*"]]; |
| const ALL=CORE.concat(EXTRA); |
| function hf(m){return (m&&m.includes("/"))?"https://huggingface.co/"+m:null;} |
| function fmt(x){return (x===null||x===undefined)?'<span class="muted">—</span>':x.toFixed(3).replace(/^0/,"");} |
| function coreMean(t){const v=CORE.map(c=>t[c[0]]).filter(x=>x!==null&&x!==undefined); |
| return v.length===CORE.length? v.reduce((a,b)=>a+b,0)/v.length : null;} |
| function chip(k){return k==="ours-commercial"?'<span class="chip c-com">commercial</span>' |
| :k==="ours-open"?'<span class="chip c-open">open</span>' |
| :k==="generalist"?'<span class="chip c-gen">generalist</span>':'<span class="chip c-cloud">cloud</span>';} |
| function render(){ |
| |
| const D=DATA.filter(r=> TAB==="vlm" ? r.kind!=="generalist" : r.kind!=="cloud"); |
| const full=D.filter(r=>coreMean(r.tracks)!==null).sort((a,b)=>coreMean(b.tracks)-coreMean(a.tracks)); |
| const part=D.filter(r=>coreMean(r.tracks)===null).sort((a,b)=>(b.tracks.doclaynet||0)-(a.tracks.doclaynet||0)); |
| const rows=full.concat(part); |
| document.getElementById("head").innerHTML='<tr><th class="rank">#</th><th>Model</th>' |
| +ALL.map(t=>`<th class="n">${t[1]}</th>`).join("")+'<th class="barcell">Doc-type mean</th><th class="n">Latency</th></tr>'; |
| const tb=document.getElementById("rows");tb.innerHTML=""; |
| const max=Math.max(...full.map(r=>coreMean(r.tracks))); |
| rows.forEach((r,i)=>{ |
| const m=coreMean(r.tracks), ours=r.kind&&r.kind.startsWith("ours"); |
| const tr=document.createElement("tr");if(ours)tr.className="ours"; |
| const nm=hf(r.model)?`<a class="name" href="${hf(r.model)}" target="_blank" rel="noopener">${r.name}</a>`:`<span class="name">${r.name}</span>`; |
| tr.innerHTML=`<td class="rank">${m!==null?i+1:''}</td><td>${nm}${chip(r.kind)}</td>` |
| +ALL.map(t=>`<td class="n ${EXTRA.some(e=>e[0]===t[0])?'oursonly':''}">${fmt(r.tracks[t[0]])}</td>`).join("") |
| +(m!==null?`<td><div class="barcell"><div class="bar ${ours?'hl':''}" style="flex:1"><span style="width:${Math.round(m/max*100)}%"></span></div><span class="barnum">${m.toFixed(3).replace(/^0/,"")}</span></div></td>` |
| :`<td class="muted" style="font-size:12px">partial coverage</td>`) |
| +`<td class="n">${r.latency||'<span class="muted">—</span>'}</td>`; |
| tb.appendChild(tr); |
| }); |
| } |
| function setBanner(){const b=document.getElementById("banner"); |
| if(DATA.some(d=>d.provisional)){b.className="tracknote";b.style.borderColor="var(--warn)";b.textContent="Provisional numbers — a clean scoring pass on standard hardware is pending.";}} |
| fetch("leaderboard.json").then(r=>r.ok?r.json():[]).then(d=>{DATA=Array.isArray(d)?d:(d.results||[]);render();}).catch(()=>render()); |
| </script> |
| </body> |
| </html> |
|
|