Skip to content

fix(metric): track none_count on Stat to surface denominator-drop (unscoreable-as-pass)#4326

Open
AUTHENSOR wants to merge 1 commit into
stanford-crfm:mainfrom
AUTHENSOR:fix/stat-none-count
Open

fix(metric): track none_count on Stat to surface denominator-drop (unscoreable-as-pass)#4326
AUTHENSOR wants to merge 1 commit into
stanford-crfm:mainfrom
AUTHENSOR:fix/stat-none-count

Conversation

@AUTHENSOR

Copy link
Copy Markdown

Summary

Stat.add(None) silently skipped values without counting them, so the reported mean excluded unscoreable samples from the denominator. A model-under-test that makes the judge unscoreable on its hardest samples (e.g. output that causes a judge parse failure or refusal) can erase those samples from its reported score — the "unscoreable-as-pass" class.

This is the same metric-integrity issue fixed in garak (NVIDIA/garak#1954) and ragas (vibrantlabsai/ragas#2832), now in HELM's Stat aggregator.

The drop

src/helm/benchmark/metrics/statistic.py:33-36:

def add(self, x) -> "Stat":
    if x is None:
        return self    # <-- count NOT incremented, sample vanishes from denominator

Several critique metrics return None on judge parse failure (e.g. gpt4_audio_critique_metrics.py:64-73 _extract_score_from_gpt_outputNone on regex miss → stats[answer_name].add(None)), which then silently drops from the mean.

Fix

Add a none_count field that tracks skipped values, and surface it in bare_str():

  • min=0.9, mean=0.9, max=0.9, sum=1.8 (2 ⚠3 skipped) when none_count > 0
  • Unchanged output when none_count == 0

This makes the denominator-drop visible in metric output without changing aggregation semantics. Callers can check stat.none_count to detect the gap between the reported mean and the true worst-case.

Note

HELM's safety_metrics.py:76-77 already defends against the all-None case (raise SafetyScoreMetricException), and wildbench_metrics.py:27-28 raises on all-failed. This fix covers the partial-drop case (some but not all samples are None) that those guards miss.

Checklist

  • Code follows project style (ruff line-length 120)
  • ruff check + ruff format pass
  • Additive — no change to existing aggregation semantics
  • Self-reviewed

Stat.add(None) silently skipped values without counting them, so the
reported mean excluded unscoreable samples from the denominator. A
model-under-test can exploit this by making the judge unscoreable on its
hardest samples, erasing them from the reported score (unscoreable-as-pass).

Add a none_count field that tracks skipped values, and surface it in
bare_str() so the denominator-drop is visible in metric output. Mirrors
the fixes shipped to garak (stanford-crfm#1954) and ragas (stanford-crfm#2832).
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.

1 participant