GlossoBench methodology¶
Status: Active · Audience: Score consumers, reviewers · Updated: 2026-07-24
How GlossoBench scores a model, why each choice, and how to reproduce a number.
Purpose¶
Explain how GlossoBench scores a model, justify each methodological choice, and let a reader reproduce any published number.
Goals¶
- Document the 7-axis scoring methodology end-to-end (axes, MS, bootstrap CI, judges, versioning).
- Make every score reproducible from pinned
bench_version+ per-plugin versions + config. - Surface measurement confidence (CIs, inter-judge κ, contamination rate) so readers can weigh numbers.
- Define what GlossoBench is NOT so it isn't misused as a promotion gate.
Success Criteria¶
python -m glossobench runwith pinnedbench_version+ per-plugin versions reproduces a score within the bootstrap CI reported in the result JSON.- Every axis ships a measurement profile (n, scorer type, inter-judge κ, contamination rate).
python -m glossobench correlate --ours ours.json --external external.jsonexits clean (0 or 2) on a shared-model_idscore set.- The doc's
**Related:**links resolve to surviving docs indocs/.
0. Design principles + meta-evaluation protocol¶
The SEA evaluation community (AI Singapore's SEA-HELM among others) has done foundational work GlossoBench builds on and complements.
Principle 1 — Validity comes before rank¶
A leaderboard number is only as good as the instrument behind it. For every axis we publish, we want a reader to be able to answer: does this score measure the capability it claims to? Concretely, each axis ships with its construct notes: what it tests, how it is scored, and the known limits of that measurement.
Principle 2 — Report the measurement's confidence, not just its point¶
- Item-level bootstrap confidence intervals on every axis (already implemented), so small-n axes visibly carry wider uncertainty rather than a falsely precise number.
- Judge-agnostic by design: any judge can be plugged in, and for judged axes we report inter-judge agreement (Cohen's κ). Where judges disagree materially, those items are surfaced as low-confidence rather than silently scored.
Principle 3 — Language coverage should be real, not proxied¶
Some safety and toxicity tooling in the SEA space covers Indonesian/Thai but not Malay, so Malay scores can end up as nearest-language or English-pivot proxies. GlossoBench's goal is native-language measurement: score Malay on Malay, avoid English-pivot on the scored path, and when any translation is unavoidable, publish the raw-vs-translated delta so consumers can see the measurement's sensitivity.
Principle 4 — Register-aware composition¶
Formal (HIGH-register) text under-samples the everyday and short-form (MID/LOW) registers where much real-world language — and much toxicity — actually lives. GlossoBench states its register mix per axis so readers know what slice of the language a score represents.
Principle 5 — Contamination transparency at task level¶
Public eval prompts can leak into training corpora, and exact-row-hash dedup can miss it when the same task appears with a different rollout. GlossoBench's aim is to publish a task-level (not just row-hash) contamination view against common corpus snapshots per version, so a score can be read as capability rather than recall.
The feature that ties it together — a per-axis "measurement profile"¶
Alongside each capability score, GlossoBench surfaces a small measurement profile:
n— eval size (so variance is visible)- scorer type — deterministic-rule / classification / LLM-judge
- inter-judge κ — for judged axes
- task-level contamination rate — vs a fixed corpus snapshot
This lets a reader weigh a high score on a large, deterministic, uncontaminated axis differently from a high score on a small, judge-scored one — without implying either is "wrong". Transparency about how each number was produced is, we think, the most useful thing a leaderboard can add.
The meta-evaluation protocol — measuring what the measurement misses¶
The claims above reduce to one testable protocol. GlossoBench is not only a benchmark; it is a meta-evaluation instrument: it measures the blind spots of an existing evaluation, on the same models, and publishes the disagreement as data. The protocol:
- Same models, both instruments. Run an identical model set on GlossoBench and on the reference evaluation (e.g. SEA-HELM). Any ranking difference is then attributable to the instruments, not the models.
- Quantify the disagreement.
glossobench correlate --ours ours.json --external seahelm.jsonreports Spearman ρ + Kendall τ + a per-model rank-delta table.converged(ρ≥0.7) means the two instruments agree;divergent(ρ<0.5) means at least one is measuring something the other does not. - Decompose WHERE they disagree. GlossoBench's separations make the divergence attributable: judge-free vs judge-dependent axes (is the gap a judge preference?), per-register scores (is the reference formal-only?), cloze-vs-generate parity (is it a scoring-mode artifact?), truncation + near-chance flags (is one instrument reading noise?), and the per-axis measurement profile (n, scorer type, κ, contamination rate).
- Publish the disagreement, not a verdict. A ranking inversion between two instruments is a finding about what each measures — colloquial-register reading comprehension vs judge-scored open-ended generation, for example — not proof that either is wrong. The first cross-instrument run produced exactly such an inversion (recorded as a directional finding, pending the formal rank-correlation test with a zero-overlap external axis set).
In one sentence: where a single benchmark asks "which model is better?", the
meta-evaluation protocol asks "what does each benchmark actually measure?" —
and GlossoBench ships the tooling to answer it (correlate, register splits,
judge-free/judged separation, measurement profiles). Coverage gaps in any
reference evaluation — registers it does not sample, languages it proxies
through neighbors, capabilities entangled with a single judge — show up as
attributable divergence rather than anecdote.
A note on how we hold ourselves accountable¶
The same standards apply to GlossoBench's own axes. Where an axis is small-n or judge-scored, its measurement profile says so plainly. Some axes we author (e.g. certain cultural or safety probes) are marked experimental / directional until independently validated, and never gate a headline ranking. The goal is a leaderboard whose numbers age well because the method behind them is stated in full.
1. Axes¶
GlossoBench-7 = Knowledge, NLU, IF, NLG, MT, Safety, Cultural. Each axis is an
AxisPlugin (see plugins/base.py). A model is scored per axis → per-item
predictions + an axis score + the metric name.
- Knowledge / NLU / Cultural — MCQ (or 3-way NLI) exact-match. Default scoring
is cloze loglik (no generation): the model's first-token logprob over each
choice picks the answer. Fast, judge-free, device-portable
(
glossobench/axes/judge_free.py+glossobench/model.py:loglik_choice_batch).scoring_mode: generateinstead has the model GENERATE a letter (parsed, exact-match) — the parity path instruct/RLHF-tuned models win on;bothruns each, headlines generate, reportsby_scoring={cloze,generate,gap,parse_fail}+ per-itemcloze_correct(the cloze-favors-base instruct-penalty diagnostic). - IF — rule-based constraint checkers (faithful port of IFEval's 21
constraint types,
glossobench/scoring/ifeval_checkers.py). Verifiable, no judge. - NLG — chrF++ (pure-python, no sacrebleu) or MetricX-24 (Google, lazy) on FLORES-200. Model-based metric, no API judge.
- MT — open-ended multi-turn, judge ensemble (std-dev agreement). No ground
truth; headline metric =
mean_judge_score_normalized(mean judge rating /10, 0-1). The win-rate-vs-5/10-threshold field (weighted_win_rate) is kept as a legacy secondary field only. - Safety — headline metric =
refusal_on_unsafe. AUPRC over judge-classified model responses (gold = prompt label) is reported as a diagnostic only. SEA-Guard-style judge (Apache, public, pluggable).
2. Mean score (MS)¶
The headline MS is ms_public — computed over the public axes only — with a
per-item pooled variant ms_pooled. Self-built axes are reported separately in
the ms_selfbuilt/ms_experimental tier ("Experimental", directional only),
never folded into the headline comparison number.
Weighted mean across axes that produced a score. Weights default to 1.0
(equal); overridable per axis in config. Skipped axes (no judge, no data) do
NOT count in the denominator — MS is over axes that ran, and skipped_no_judge
is always reported so a 5-axis MS is never silently mistaken for a 7-axis MS.
3. Bootstrap CI¶
30–2000 bootstrap resamples (resample axes with replacement, weight-preserving) → 95% percentile CI on MS. Per-axis CIs come from the axis scorer (e.g. NLG bootstraps chrF across items). Low-CI items flagged.
4. Judge-agnostic design¶
- Verifiable axes (Knowledge/NLU/IF/NLG/Cultural) NEVER let a judge OVERRIDE the score. By default they never call one at all.
- Optional judge OVERLAY (
judge_overlay: trueper axis or--judge-overlay): a verifiable axis ALSO asks the judge to rate each generated response 0-10, stashed as a SECONDARYjudge_qualityfield. The verifiable score stays the headline. Byte-identical to legacy when off; cloze-only items (no generated response) getjudge_quality: null. Lets a judge-free run add a quality reading (e.g. judge-rate chrF-scored translations for semantic adequacy) without losing the honest verifiable score. - Judge axes (MT/Safety) get a
JudgeEnsembleof NJudgePlugins. Score = mean(rating); agreement = 1 − std/|mean|. agreement < floor → low_conf flag. The judgesystem_promptis language-parametric (empty = Malay default forms; non-ms configs set a neutral English instruction so the judge does not instruct in Malay for Thai/Vietnamese/Indonesian responses). - No judges configured → judge axes skip + flag; framework never hard-blocks.
- Elo runs on ground-truth exact-match (verifiable axes) even with zero judges;
judge axes ADD pairwise matches for confidence. See
elo/__init__.py. - Near-chance axes (
summary["near_chance_axes"], per-axisdiscriminates): an axis whose chance-normalized score ≤MB_NEAR_CHANCE_EPS(default 0.05) is reported but should be EXCLUDED from cross-model ranking — it carries noise, not signal (e.g. Global-MMLU Knowledge when every model is near chance). --chat-template auto|always:alwaysapplies the model's OWN chat_template on the generate path so a chat-tuned model is scored fair; NLU/Knowledge cloze stays raw (correct for cloze).auto(default) = byte-identical legacy.
5. Versioning + immutability¶
Every output records bench_version, schema_version, per-plugin version,
and model_id (model path/sha). Two runs with identical pins must reproduce
within bootstrap CI; if not, it's logged as non-determinism, not papered over.
Leaderboards live at glossobench/leaderboard/<lang>/v<MAJOR>/leaderboard.jsonl
and are append-only. v2 never overwrites v1. glossobench compare v1 v2 shows
per-axis deltas for models that ran both.
6. Held-out + contamination¶
- GlossoBench data never enters any training corpus (separate gate, separate dir).
- Before freeze: hard-EXCLUDE contamination against every train file
(
tools/dedup.py:build_corpus_index+dedup_exclude_items, fired byMB_DEDUP_CORPUS). Items whose prompt exact-substring-matches OR has shingle-Jaccard ≥ threshold vs the train corpus are DROPPED BEFORE scoring (not flagged-and-kept), and a SHA-256 of the sorted kept item-id set is published — the reproducible fingerprint of the EXACT scored subset (summary["dedup"]). A contaminated item can't inflate a score if it is never scored. - Adversarial probes (
probes: [shuffle, unanswerable]) catch leakage + format-gaming that dedup misses: a position-memorizer collapses on shuffle, a format-memorizer picks its fixed letter on the unanswerable probe → wrong. - Frozen versioned (
glossobench-v1); bump to v2 to change data.
7. Budget enforcement¶
--max-wall-seconds 10800 (3h default). The runner computes a per-axis budget
from the global deadline; axes early-stop (logged). Nothing is silently
truncated — dropped rows/axes are always in the output notes.
8. Reproducing a number¶
- Pin
bench_version+ per-plugin versions (in the result JSON). glossobench run --model <same path> --model-id <same id> --config <same yaml>.- Same harness, same data, same judge config → same score within CI.
- To compare to a third party: run their model on the SAME config. Numbers that differ = real signal; numbers that don't = the harness is doing its job.
9. What GlossoBench is NOT¶
- Not a gate (Standing Order 8 is the project's promotion gate; GlossoBench is the measurement layer beneath it).
- Not a replacement for the DATA of MalayMMLU/FLORES/belebele — it HOSTS them.
- Not a neutral 3rd-party benchmark by authorship — credibility comes from public sources + third-party models on the same harness + open-source + CI, not from who wrote it.
10. Fairness methodology (the "is the win real / is the bench neutral" layer)¶
A score is only worth trusting if the bench is neutral and the win is real. 11 levers ship; each targets a specific threat to credibility. They are ADDITIVE (schema-1.0/1.1-compatible; additive) — opting out is byte-identical to legacy.
- Cloze-favors-base bias (#8). Cloze loglik
P(choice|prompt)favors raw/base over instruct/RLHF-tuned (chat-tuning shifts the output distribution + flattens the tail).scoring_mode: bothexposes the gap (gap_cloze_minus_generate) so a base>instruct cloze artifact is visible, not read as "base understands better". - Contamination (#5) + memorization (#1). Hard-EXCLUDE drops train-overlap
items before scoring + publishes the scored-subset SHA (§6). Probes
(
shuffle/unanswerable) catch the memorization dedup misses — a real comprehension model stays; a memorizer collapses (shuffle_drop,unanswerable_drop= leakage signal). - Self-judge + cross-class artifacts (#2, #3).
MB_MODELS_METAstampsmodel_meta(family, model_class, params) so a leaderboard segregates by class (a base>instruct cloze artifact is a within-group comparison, not cross-group).JudgePlugin.familyauto-drops a judge of the model's own family (Qwen-judge ≠ Qwen model, symmetric). - Truncation artifact (#9).
truncation_rateper axis flags a low score that is really an over-think cap-hit (a reasoning model cut atmax_new_tokens), not weakness. ≥30% → WARNING. - Independence (#4).
glossobench correlaterank-correlates our scores vs an external benchmark (SEA-HELM/MalayMMLU) on the same models (Spearman ρ + Kendall τ, pure-python).converged(ρ≥0.7) = independently validated;divergent(<0.5) = gameable (exit 2 → CI gate). - Auditability (#7).
glossobench releasebundles the scored items into one reproducible JSONL + manifest (versions, dataset-license MIN, dedup SHA);rawdropped by default (PII/leak). A score a third party can re-derive. - Measurement validity (#10).
glossobench calibratechecks per-difficulty- band accuracy vs a human-expected band + easy→hard monotonicity. Miscalibrated = the bench is not measuring what it claims (or the anchors are mistagged). - Apples-to-apples (#11).
--strict-parityonsigtest/compare/leaderboardgates on reasoning-mode mismatch — a thinking-on vs thinking-off comparison is a confound, not fair. - Gold-label audit (#6).
glossobench spotcheckemits a high-leverage sample worksheet (unvalidated datasets + wrong/scoring-disagreement items) for a fluent Malay reader to confirm gold keys.DatasetPlugin.human_validatedtargets the unvalidated set. The worksheet'sexpected_difficultycolumn dual-uses the same human pass for calibration anchors (#10). The human pass is required — the mechanism makes it tractable, it does not validate gold itself.
A "win" on GlossoBench is credible only with: public sources + third-party models on the same harness + open-source + held-out + CI + (now) independence / contamination / parity checks. All ship here.
11. Validating GlossoBench against public leaderboards¶
A new benchmark is only trustworthy if its rankings broadly agree with
independently-built public leaderboards on the same models — and disagreements
are explained, not hidden. The concord command makes that check a one-liner:
glossobench concord \
--scores glossobench/output/ms/v1 \ # our run dir (or a model_id,score CSV)
--external my_public_leaderboard.csv \ # model_id,score (see docs/examples/)
--name "public-leaderboard-X" \
--out concord_x.json
It aligns the two tables on shared model_ids and reports Spearman rho +
Kendall tau (tie-aware, pure-python), a Monte-Carlo permutation p-value, and a
per-model rank table with rank deltas. External table format is deliberately
generic — model_id,score, header optional (example:
docs/examples/external_scores_example.csv); rename rows so ids match yours.
Reading the result honestly:
- strong (rho ≥ 0.7): the two rankings largely agree — a good external anchor.
- moderate / weak: investigate the per-model rank deltas before concluding
anything; either side may weight different competencies.
- Small n caveat: below ~5 shared models even rho = 1.0 is not significant at
0.05 (the p-note says so) — treat it as directional.
- This is a concordance reading, not an independence proof: if the external
leaderboard shares scored items with a GlossoBench axis, high agreement is
partly tautological. For a certified-disjoint independence check use
glossobench correlate --overlap-free (audit C11).
Definition of Done¶
- Sections §0 through §11 remain present and verbatim (a
grep -E '^## [0-9]+\.'lists §0–§11 in order). **Related:**footer links resolve to FAIREST.md, SPEC_v1.0.md, and CRITIC_AUDIT.md underdocs/.- A reader can reproduce a number end-to-end by following §8 from the pinned result JSON.
python -m glossobench correlate --ours ours.json --external external.jsonandglossobench concordboth exit clean on a shared-model_idscore set.
Related: FAIREST.md · SPEC_v1.0.md · CRITIC_AUDIT.md · COMPARISON.md