Skip to content

fix(metrics): name numeric dimensions in metric labels - #1856

Merged
Yunnglin merged 3 commits into
modelscope:mainfrom
Thanatos9404:fix-metric-dimension-labels
Oct 15, 2026
Merged

Yunnglin merged 3 commits into
modelscope:mainfrom
Thanatos9404:fix-metric-dimension-labels

Conversation

@Thanatos9404

Copy link
Copy Markdown
Contributor

Fixes #1854

Summary

format_metric_label and its frontend mirror formatMetricIdentityLabel joined dimension values without their keys, so numeric dimensions lost their meaning (Accuracy ↑ · 1000 · 0). Both now look the key up in a small axis-name table and render Label value:

Dimension Label Emitted by
context_length, depth_percent Context, Depth needle_haystack
min_tokens, max_tokens Min tokens, Max tokens openai_mrcr
threshold Threshold ref_adv_s
ngram n-gram BLEU, ROUGE-1/2 (general_qa, general_vqa)
k k pass@k, pass^k, vote@k aggregations, wide_search

These are the only numeric keys that adapters, aggregators and the legacy name rules emit today. Every other dimension (scope, target, level, variant, statistic, question_type, task, topic, ...) keeps its bare value.

Accuracy ↑ · 1000 · 0         ->  Accuracy ↑ · Context 1000 · Depth 0
Score ↑ · 8192 · 4096         ->  Score ↑ · Min tokens 4096 · Max tokens 8192
Accuracy ↑ · Overall · 0.5    ->  Accuracy ↑ · Overall · Threshold 0.5
Pass@k ↑ · 1                  ->  Pass@k ↑ · k 1
ROUGE ↑ · 1 · Recall          ->  ROUGE ↑ · n-gram 1 · Recall
F1 ↑ · Row · All · 4          ->  F1 ↑ · Row · All · k 4        (wide_search Max@N)
ROUGE ↑ · L · Recall          ->  unchanged

The metric names come from the catalog, so they differ from the examples in the issue: openai_mrcr is Score ↑ (quality.score.ratio), not MRCR Score ↑, and pass@k is Pass@k ↑ · k 1. This PR only adds the axis names.

The sort order is the same except for one entry: max_tokens now sorts after min_tokens (the alphabetical tie-break put it first), so the openai_mrcr bucket reads lower bound first as in the issue. Only display text changes; MetricIdentity, identity keys, the cache and scores are untouched, so there is no evaluation_version bump.

All three surfaces

The CLI table (gen_table) and the HTML report already labelled metrics through format_metric_labels. On the web, the report header, Overview and Compare use formatMetricIdentityLabel, but the Details tab, the only web view that lists the needle_haystack cells, labelled rows by metric name only and showed Accuracy ↑ (accuracy:mean[context_length=1000,depth_percent=0]). formatMetricLabels now mirrors format_metric_labels: it takes identities, labels each one with formatMetricIdentityLabel, keys the result by identity key and appends the key only when a label repeats. ReportDetailPage builds these labels from the report's metrics and passes them to DetailsTab, which now shows Accuracy ↑ · Context 1000 · Depth 0 like the CLI table and the HTML report. The dimension order table in metricFormat.ts also became a module-level Map, like the new label table.

The dashboard's aggregated results table (AggregatedResults.tsx) still labels each row with formatMetricLabel, so it shows the primary metric name without dimensions (Accuracy ↑). It never showed bare numbers, so I left it for a follow-up.

The example tables in docs/{en,zh} (custom dataset LLM and VLM, tau_bench, tau2_bench, arena) now show the new dimension text (BLEU ↑ · n-gram 1, ROUGE ↑ · n-gram 1 · Recall, Accuracy ↑ · k 1); nothing else in those tables changed.

Validation

golden_samples.json: label-single-dimension (threshold=0.5) now expects Accuracy ↑ · Threshold 0.5, and four new label samples cover a needle_haystack cell, the openai_mrcr token range (with the quality.score.ratio entry added to the registry), wide_search's k next to word dimensions, and ngram with statistic. test_golden_samples.py and the vitest golden test both read them, and the coverage check now requires a label sample for every key in the axis-name table. test_formatting.py and metricFormat.test.ts add the same five cases; metricFormat.test.ts also covers the new formatMetricLabels, and DetailsTab.test.tsx renders needle_haystack rows and expects Accuracy ↑ · Context 1000 · Depth 0.

Results (Windows):

# source reverted to f0fac746, tests kept
pytest tests/report/semantics -p no:warnings -q --continue-on-collection-errors
                                                           # 5 failed, 811 passed, 1 error
                                                           # (test_golden_samples.py imports the new label table)
npx vitest run src/domain/metric src/components/reports/DetailsTab.test.tsx
                                                           # 9 failed, 39 passed

# this branch
pytest tests/report/semantics -p no:warnings -q          # 872 passed (Python 3.10 and 3.12)
pytest tests/report tests/metrics tests/service/test_eval_semantic_table.py \
  tests/benchmark/test_one_million_bench.py tests/benchmark/test_needle_haystack.py -p no:warnings -q
                                                           # 1218 passed, 1 skipped
npx vitest run                                            # 477 passed
npx tsc -b; npm run lint                                  # clean
pre-commit run --files <changed files>                    # all passed
lint-imports                                              # 5 kept, 0 broken

A needle_haystack report built through the adapter (calculate_metrics, aggregate_scores, generate_report; no dataset, tokenizer or model) prints this in gen_table; the HTML report and the dashboard's Details tab (built SPA served by evalscope service) show the same labels. On main the cells read Accuracy ↑ · 1000 · 0 in the CLI and HTML and Accuracy ↑ (accuracy:mean[...]) in the Details tab.

│ mock │ Needle-in-a-Haystack │ Accuracy ↑ · Overall                 │ english │ 6 │ 66.7% │
│ mock │ Needle-in-a-Haystack │ Accuracy ↑ · Context 1000 · Depth 0  │ english │ 2 │ 100%  │
│ mock │ Needle-in-a-Haystack │ Accuracy ↑ · Context 1000 · Depth 50 │ english │ 2 │ 50%   │
│ mock │ Needle-in-a-Haystack │ Accuracy ↑ · Context 2000 · Depth 0  │ english │ 2 │ 50%   │

Thanatos9404 and others added 3 commits October 15, 2026 01:35
Metric labels joined dimension values without their keys, so a
needle_haystack cell read `Accuracy ↑ · 1000 · 0` and an openai_mrcr
bucket `Score ↑ · 8192 · 4096`. Word values such as `Overall` or
`Answer` read fine on their own, numbers do not.

`format_metric_label` and the frontend `formatMetricIdentityLabel` now
put a short axis name before the numeric dimensions: context_length
(Context), depth_percent (Depth), min_tokens and max_tokens (Min tokens,
Max tokens), threshold (Threshold), ngram (n-gram) and k (k). Every
other dimension keeps its bare value. max_tokens now sorts after
min_tokens, so a token range reads lower bound first. Only the display
text changes; identities, cache keys and scores are untouched.

The web report Details tab labelled its rows by metric name only, so
the needle_haystack cells showed `Accuracy ↑ (accuracy:mean[...])`.
`formatMetricLabels` now mirrors the backend `format_metric_labels`: it
labels each identity and adds the identity key only when a label
repeats. The report page passes these labels to the Details tab, which
now matches the CLI table and the HTML report. The dashboard's
aggregated results table still shows only the primary metric name and
is left for a follow-up.

The shared golden samples pin the new labels for both implementations,
and the example tables in the docs show the new BLEU, ROUGE and tau
labels.

Fixes modelscope#1854
accuracy + pass_hat_k now resolves to metric_name="Pass^k" instead of
sharing "Pass@k" with pass_at_k.  Only the display name changes; the
semantic_id stays quality.pass_at_k.ratio and no cache or score is
affected.

Changes:
- catalog.py: AGGREGATION_SEMANTICS entry for (accuracy, pass_hat_k)
  now carries metric_name="Pass^k".
- golden_samples.json: new pass_hat_k semantics entry and label sample.
- test_resolver.py: parametrized test covers both aggregation names.
- docs tau_bench/tau2_bench (en+zh): example tables now show Pass^k.
The text-block grid tables had misaligned column separators since
before this PR.  A script now computes the max content width per
column and rebuilds every separator line to match, across all 10
affected doc files (en + zh).

@Yunnglin Yunnglin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the original dimension-label fix is clean and well tested; the two follow-up commits add:

  1. Pass^k display name: accuracy + pass_hat_k now resolves to metric_name="Pass^k" (catalog.py override), so tau_bench / arc_agi_2 show Pass^k ↑ · k 1 instead of the misleading Pass@k ↑ · k 1. semantic_id unchanged, no cache/score impact.
  2. Table alignment: all 24 grid tables across the 10 doc files now have separators matching the widest cell per column.

Local verification: 875 Python tests passed (tests/report/semantics full suite), 41 vitest passed (metricFormat.test.ts), ruff clean. CI green on all three commits.

@Yunnglin
Yunnglin merged commit fc9955e into modelscope:main Oct 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Metric labels drop dimension names: numeric dimensions render unreadable (e.g. Accuracy ↑ · 1000 · 0)

2 participants